diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index c706eaa1599b..baf7373191c5 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -164,48 +164,43 @@ def test_ScalarFormatter_offset_value(): fig, ax = plt.subplots() formatter = ax.get_xaxis().get_major_formatter() - def update_ticks(ax): - return next(ax.get_xaxis().iter_ticks()) - - ax.set_xlim(123, 189) - update_ticks(ax) - assert_equal(formatter.offset, 0) - - ax.set_xlim(-189, -123) - update_ticks(ax) - assert_equal(formatter.offset, 0) - - ax.set_xlim(12341, 12349) - update_ticks(ax) - assert_equal(formatter.offset, 12340) - - ax.set_xlim(-12349, -12341) - update_ticks(ax) - assert_equal(formatter.offset, -12340) - - ax.set_xlim(99999.5, 100010.5) - update_ticks(ax) - assert_equal(formatter.offset, 100000) - - ax.set_xlim(-100010.5, -99999.5) - update_ticks(ax) - assert_equal(formatter.offset, -100000) - - ax.set_xlim(99990.5, 100000.5) - update_ticks(ax) - assert_equal(formatter.offset, 100000) - - ax.set_xlim(-100000.5, -99990.5) - update_ticks(ax) - assert_equal(formatter.offset, -100000) - - ax.set_xlim(1233999, 1234001) - update_ticks(ax) - assert_equal(formatter.offset, 1234000) - - ax.set_xlim(-1234001, -1233999) - update_ticks(ax) - assert_equal(formatter.offset, -1234000) + def check_offset_for(left, right, offset): + ax.set_xlim(left, right) + # Update ticks. + next(ax.get_xaxis().iter_ticks()) + assert_equal(formatter.offset, offset) + + test_data = [(123, 189, 0), + (-189, -123, 0), + (12341, 12349, 12340), + (-12349, -12341, -12340), + (99999.5, 100010.5, 100000), + (-100010.5, -99999.5, -100000), + (99990.5, 100000.5, 100000), + (-100000.5, -99990.5, -100000), + (1233999, 1234001, 1234000), + (-1234001, -1233999, -1234000), + # Test cases courtesy of @WeatherGod + (.4538, .4578, .45), + (3789.12, 3783.1, 3780), + (45124.3, 45831.75, 45000), + (0.000721, 0.0007243, 0.00072), + (12592.82, 12591.43, 12590), + (9., 12., 0), + (900., 1200., 0), + (1900., 1200., 0), + (0.99, 1.01, 1), + (9.99, 10.01, 10), + (99.99, 100.01, 100), + (5.99, 6.01, 6), + (15.99, 16.01, 16), + (-0.452, 0.492, 0), + (-0.492, 0.492, 0), + (12331.4, 12350.5, 12300), + (-12335.3, 12335.3, 0)] + + for left, right, offset in test_data: + yield check_offset_for, left, right, offset def _logfe_helper(formatter, base, locs, i, expected_result): diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index ecba335d5483..12781fa520a2 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -567,34 +567,38 @@ def _compute_offset(self): lmin, lmax = locs.min(), locs.max() # min, max comparing absolute values (we want division to round towards # zero so we work on absolute values). - abs_min, abs_max = sorted(map(abs, [lmin, lmax])) - # Only use offset if there are at least two ticks, every tick has the - # same sign, and if the span is small compared to the absolute values. - if (lmin == lmax or lmin <= 0 <= lmax or - (abs_max - abs_min) / abs_max >= 1e-2): + abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))]) + # Only use offset if there are at least two ticks and every tick has + # the same sign. + if lmin == lmax or lmin <= 0 <= lmax: self.offset = 0 return sign = math.copysign(1, lmin) # What is the smallest power of ten such that abs_min and abs_max are # equal up to that precision? - oom = 10 ** int(math.log10(abs_max) + 1) + # Note: Internally using oom instead of 10 ** oom avoids some numerical + # accuracy issues. + oom = math.ceil(math.log10(abs_max)) while True: - if abs_min // oom != abs_max // oom: - oom *= 10 + if abs_min // 10 ** oom != abs_max // 10 ** oom: + oom += 1 break - oom /= 10 - if (abs_max - abs_min) / oom <= 1e-2: + oom -= 1 + if (abs_max - abs_min) / 10 ** oom <= 1e-2: # Handle the case of straddling a multiple of a large power of ten # (relative to the span). # What is the smallest power of ten such that abs_min and abs_max # at most 1 apart? - oom = 10 ** int(math.log10(abs_max) + 1) + oom = math.ceil(math.log10(abs_max)) while True: - if abs_max // oom - abs_min // oom > 1: - oom *= 10 + if abs_max // 10 ** oom - abs_min // 10 ** oom > 1: + oom += 1 break - oom /= 10 - self.offset = sign * (abs_max // oom) * oom + oom -= 1 + # Only use offset if it saves at least two significant digits. + self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom + if abs_max // 10 ** oom >= 10 + else 0) def _set_orderOfMagnitude(self, range): # if scientific notation is to be used, find the appropriate exponent