# HG changeset patch # User Chris Cannam # Date 1525357446 -3600 # Node ID 45519a9836e68d0517a861e1927f4b768590d0d1 # Parent 0fb5d4e6edeb7ea21d9827417fa11fa72055de19# Parent 9528c73aa98c6f0724922d2d5fc86f7710e81e8f Merge from branch horizontal-scale diff -r 0fb5d4e6edeb -r 45519a9836e6 base/ScaleTickIntervals.h --- 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)); diff -r 0fb5d4e6edeb -r 45519a9836e6 base/test/TestScaleTickIntervals.h --- 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