changeset 1461:45519a9836e6

Merge from branch horizontal-scale
author Chris Cannam
date Thu, 03 May 2018 15:24:06 +0100
parents 0fb5d4e6edeb (current diff) 9528c73aa98c (diff)
children aab2d7177d3d
files
diffstat 2 files changed, 173 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/base/ScaleTickIntervals.h	Tue Apr 24 15:02:54 2018 +0100
+++ b/base/ScaleTickIntervals.h	Thu May 03 15:24:06 2018 +0100
@@ -83,6 +83,7 @@
         double limit;      // max from original range
         double spacing;    // increment between ticks
         double roundTo;    // what all displayed values should be rounded to
+                           // (if 0.0, then calculate based on precision)
         Display display;   // whether to use fixed precision (%e, %f, or %g)
         int precision;     // number of dp (%f) or sf (%e)
         bool logUnmap;     // true if values represent logs of display values
@@ -182,6 +183,11 @@
     {
         Display display = Auto;
 
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+        SVDEBUG << "ScaleTickIntervals::logInstruction: Range is "
+                << r.min << " to " << r.max << endl;
+#endif
+        
         if (r.n < 1) {
             return {};
         }
@@ -194,56 +200,47 @@
         
         double inc = (r.max - r.min) / r.n;
 
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+        SVDEBUG << "ScaleTickIntervals::logInstruction: "
+                << "Naive increment is " << inc << endl;
+#endif
+
+        int precision = 1;
+
+        if (inc < 1.0) {
+            precision = int(ceil(1.0 - inc)) + 1;
+        }
+
         double digInc = log10(inc);
         int precInc = int(floor(digInc));
-        double roundTo = pow(10.0, precInc);
+        double roundIncTo = pow(10.0, precInc);
 
-        if (roundTo != 0.0) {
-            inc = round(inc / roundTo) * roundTo;
-            if (inc < roundTo) inc = roundTo;
-        }
+        inc = round(inc / roundIncTo) * roundIncTo;
+        if (inc < roundIncTo) inc = roundIncTo;
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+        SVDEBUG << "ScaleTickIntervals::logInstruction: "
+                << "Rounded increment to " << inc << endl;
+#endif
 
         // if inc is close to giving us powers of two, nudge it
         if (fabs(inc - 0.301) < 0.01) {
             inc = log10(2.0);
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+            SVDEBUG << "ScaleTickIntervals::logInstruction: "
+                    << "Nudged increment to " << inc << " to get powers of two"
+                    << endl;
+#endif
         }
 
-        // smallest increment as displayed
-        double minDispInc =
-            LogRange::unmap(r.min + inc) - LogRange::unmap(r.min);
-
-        int prec = 1;
-
-        if (minDispInc > 0.0) {
-            prec = int(floor(log10(minDispInc)));
-            if (prec < 0) prec = -prec;
-        }
-
-        if (r.max >= -2.0 && r.max <= 3.0 &&
-            r.min >= -3.0 && r.min <= 3.0) {
-            display = Fixed;
-            if (prec == 0) prec = 1;
-        }
-
-#ifdef DEBUG_SCALE_TICK_INTERVALS
-        SVDEBUG << "ScaleTickIntervals: calculating logInstruction" << endl
-                << "ScaleTickIntervals: min = " << r.min << ", max = " << r.max
-                << ", n = " << r.n << ", inc = " << inc
-                << ", minDispInc = " << minDispInc << ", digInc = " << digInc
-                << endl;
-        SVDEBUG << "ScaleTickIntervals: display = " << display
-                << ", inc = " << inc << ", precInc = " << precInc
-                << ", prec = " << prec << endl;
-        SVDEBUG << "ScaleTickIntervals: roundTo = " << roundTo << endl;
-#endif
-        
         double min = r.min;
         if (inc != 0.0) {
             min = ceil(r.min / inc) * inc;
             if (min > r.max) min = r.max;
         }
 
-        return { min, r.max, inc, 0.0, display, prec, true };
+        return { min, r.max, inc, 0.0, display, precision, true };
     }
 
     static Ticks linearTicks(Range r) {
@@ -259,16 +256,52 @@
     }
     
     static Tick makeTick(Display display, int precision, double value) {
+
         if (value == -0.0) {
             value = 0.0;
         }
+        
         const int buflen = 40;
         char buffer[buflen];
-        snprintf(buffer, buflen,
-                 display == Auto ? "%.*g" :
-                 display == Fixed ? "%.*f" :
-                 "%.*e",
-                 precision, value);
+
+        if (display == Auto) {
+
+            int digits = (value != 0.0 ? 1 + int(floor(log10(abs(value)))) : 0);
+
+            // This is not the same logic as %g uses for determining
+            // whether to delegate to use scientific or fixed notation
+
+            if (digits < -3 || digits > 4) {
+
+                display = Auto; // delegate planning to %g
+
+            } else {
+
+                display = Fixed;
+                
+                // in %.*f, the * indicates decimal places, not sig figs
+                if (precision >= digits) {
+                    precision -= digits;
+                } else {
+                    precision = 0;
+                }
+            }
+        }
+
+        const char *spec = (display == Auto ? "%.*g" :
+                            display == Scientific ? "%.*e" :
+                            "%.*f");
+
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+        
+        snprintf(buffer, buflen, spec, precision, value);
+
+#ifdef DEBUG_SCALE_TICK_INTERVALS
+        SVDEBUG << "makeTick: spec = \"" << spec
+                << "\", prec = " << precision << ", value = " << value
+                << ", label = \"" << buffer << "\"" << endl;
+#endif
+        
         return Tick({ value, std::string(buffer) });
     }
     
@@ -301,16 +334,30 @@
         Ticks ticks;
         
         while (true) {
+
             double value = instruction.initial + n * instruction.spacing;
+
             if (value >= max + eps) {
                 break;
             }
+
             if (instruction.logUnmap) {
                 value = pow(10.0, value);
             }
-            if (instruction.roundTo != 0.0) {
-                value = instruction.roundTo * round(value / instruction.roundTo);
+
+            double roundTo = instruction.roundTo;
+
+            if (roundTo == 0.0 && value != 0.0) {
+                // We don't want the internal value secretly not
+                // matching the displayed one
+                roundTo =
+                    pow(10, ceil(log10(abs(value))) - instruction.precision);
             }
+                                           
+            if (roundTo != 0.0) {
+                value = roundTo * round(value / roundTo);
+            }
+            
             ticks.push_back(makeTick(instruction.display,
                                      instruction.precision,
                                      value));
--- a/base/test/TestScaleTickIntervals.h	Tue Apr 24 15:02:54 2018 +0100
+++ b/base/test/TestScaleTickIntervals.h	Thu May 03 15:24:06 2018 +0100
@@ -438,7 +438,7 @@
         // pathological range
         auto ticks = ScaleTickIntervals::linear({ 0, 0, 10 });
         ScaleTickIntervals::Ticks expected {
-            { 0.0, "0" }
+            { 0.0, "0.0" }
         };
         compareTicks(ticks, expected);
     }
@@ -458,7 +458,7 @@
         // senseless input
         auto ticks = ScaleTickIntervals::linear({ 0, 1, 0 });
         ScaleTickIntervals::Ticks expected {
-            { 0.0, "0" },
+            { 0.0, "0.0" },
         };
         compareTicks(ticks, expected);
     }
@@ -468,7 +468,7 @@
         // senseless input
         auto ticks = ScaleTickIntervals::linear({ 0, 1, -1 });
         ScaleTickIntervals::Ticks expected {
-            { 0.0, "0" },
+            { 0.0, "0.0" },
         };
         compareTicks(ticks, expected);
     }
@@ -498,8 +498,8 @@
         auto ticks = ScaleTickIntervals::logarithmic({ 1, 10, 2 });
         ScaleTickIntervals::Ticks expected {
             { 1.0, "1.0" },
-            { pow(10.0, 0.5), "3.2" },
-            { 10.0, "10.0" },
+            { 3.2, "3.2" },
+            { 10.0, "10" },
         };
         compareTicks(ticks, expected);
     }
@@ -518,7 +518,7 @@
     {
         auto ticks = ScaleTickIntervals::logarithmic({ M_PI, 6.022140857e23, 7 });
         ScaleTickIntervals::Ticks expected {
-            { 1000, "1e+03" },
+            { 1000, "1000" },
             { 1e+06, "1e+06" },
             { 1e+09, "1e+09" },
             { 1e+12, "1e+12" },
@@ -528,26 +528,96 @@
         };
         compareTicks(ticks, expected, true);
     }
-    
+
     void log_0p465_778_10()
     {
         auto ticks = ScaleTickIntervals::logarithmic({ 0.465, 778.08, 10 });
         ScaleTickIntervals::Ticks expected {
-            { 0.5, "0.5" },
+            { 0.5, "0.50" },
             { 1, "1.0" },
             { 2, "2.0" },
             { 4, "4.0" },
             { 8, "8.0" },
-            { 16, "16.0" },
-            { 32, "32.0" },
-            { 64, "64.0" },
-            { 128, "128.0" },
-            { 256, "256.0" },
-            { 512, "512.0" },
+            { 16, "16" },
+            { 32, "32" },
+            { 64, "64" },
+            { 130, "130" },
+            { 260, "260" },
+            { 510, "510" },
         };
         compareTicks(ticks, expected);
     }
     
+    void log_1_10k_10()
+    {
+        auto ticks = ScaleTickIntervals::logarithmic({ 1.0, 10000.0, 10 });
+        ScaleTickIntervals::Ticks expected {
+            { 1.0, "1.0" },
+            { 2.5, "2.5" },
+            { 6.3, "6.3" },
+            { 16.0, "16" },
+            { 40.0, "40" },
+            { 100.0, "100" },
+            { 250.0, "250" },
+            { 630.0, "630" },
+            { 1600.0, "1600" },
+            { 4000.0, "4000" },
+            { 10000.0, "1e+04" },
+        };
+        compareTicks(ticks, expected, true);
+    }
+    
+    void log_80_10k_6()
+    {
+        auto ticks = ScaleTickIntervals::logarithmic({ 80.0, 10000.0, 6 });
+        ScaleTickIntervals::Ticks expected {
+            { 130, "130" },
+            { 260, "260" },
+            { 510, "510" },
+            { 1000, "1000" },
+            { 2000, "2000" },
+            { 4100, "4100" },
+            { 8200, "8200" }
+        };
+        compareTicks(ticks, expected, true);
+    }
+    
+    void log_80_800k_10()
+    {
+        auto ticks = ScaleTickIntervals::logarithmic({ 80.0, 800000.0, 10 });
+        ScaleTickIntervals::Ticks expected {
+            { 100, "100" },
+            { 250, "250" },
+            { 630, "630" },
+            { 1600, "1600" },
+            { 4000, "4000" },
+            { 10000, "1e+04" },
+            { 25000, "2.5e+04" },
+            { 63000, "6.3e+04" },
+            { 160000, "1.6e+05" },
+            { 400000, "4e+05" },
+        };
+        compareTicks(ticks, expected, true);
+    }
+    
+    void log_0_1_0()
+    {
+        // senseless input
+        auto ticks = ScaleTickIntervals::logarithmic({ 0, 1, 0 });
+        ScaleTickIntervals::Ticks expected {
+        };
+        compareTicks(ticks, expected);
+    }
+    
+    void log_0_1_m1()
+    {
+        // senseless input
+        auto ticks = ScaleTickIntervals::logarithmic({ 0, 1, -1 });
+        ScaleTickIntervals::Ticks expected {
+        };
+        compareTicks(ticks, expected);
+    }
+
 };
 
 #endif