Skip to content

Commit 0a9df25

Browse files
committed
Only use offset if it saves >=2 sig. digits.
Tests cases courtesy of @WeatherGod. Slightly improved numerical accuracy.
1 parent 226b996 commit 0a9df25

File tree

2 files changed

+56
-57
lines changed

2 files changed

+56
-57
lines changed

lib/matplotlib/tests/test_ticker.py

+37-42
Original file line numberDiff line numberDiff line change
@@ -164,48 +164,43 @@ def test_ScalarFormatter_offset_value():
164164
fig, ax = plt.subplots()
165165
formatter = ax.get_xaxis().get_major_formatter()
166166

167-
def update_ticks(ax):
168-
return next(ax.get_xaxis().iter_ticks())
169-
170-
ax.set_xlim(123, 189)
171-
update_ticks(ax)
172-
assert_equal(formatter.offset, 0)
173-
174-
ax.set_xlim(-189, -123)
175-
update_ticks(ax)
176-
assert_equal(formatter.offset, 0)
177-
178-
ax.set_xlim(12341, 12349)
179-
update_ticks(ax)
180-
assert_equal(formatter.offset, 12340)
181-
182-
ax.set_xlim(-12349, -12341)
183-
update_ticks(ax)
184-
assert_equal(formatter.offset, -12340)
185-
186-
ax.set_xlim(99999.5, 100010.5)
187-
update_ticks(ax)
188-
assert_equal(formatter.offset, 100000)
189-
190-
ax.set_xlim(-100010.5, -99999.5)
191-
update_ticks(ax)
192-
assert_equal(formatter.offset, -100000)
193-
194-
ax.set_xlim(99990.5, 100000.5)
195-
update_ticks(ax)
196-
assert_equal(formatter.offset, 100000)
197-
198-
ax.set_xlim(-100000.5, -99990.5)
199-
update_ticks(ax)
200-
assert_equal(formatter.offset, -100000)
201-
202-
ax.set_xlim(1233999, 1234001)
203-
update_ticks(ax)
204-
assert_equal(formatter.offset, 1234000)
205-
206-
ax.set_xlim(-1234001, -1233999)
207-
update_ticks(ax)
208-
assert_equal(formatter.offset, -1234000)
167+
def check_offset_for(left, right, offset):
168+
ax.set_xlim(left, right)
169+
# Update ticks.
170+
next(ax.get_xaxis().iter_ticks())
171+
assert_equal(formatter.offset, offset)
172+
173+
test_data = [(123, 189, 0),
174+
(-189, -123, 0),
175+
(12341, 12349, 12340),
176+
(-12349, -12341, -12340),
177+
(99999.5, 100010.5, 100000),
178+
(-100010.5, -99999.5, -100000),
179+
(99990.5, 100000.5, 100000),
180+
(-100000.5, -99990.5, -100000),
181+
(1233999, 1234001, 1234000),
182+
(-1234001, -1233999, -1234000),
183+
# Test cases courtesy of @WeatherGod
184+
(.4538, .4578, .45),
185+
(3789.12, 3783.1, 3780),
186+
(45124.3, 45831.75, 45000),
187+
(0.000721, 0.0007243, 0.00072),
188+
(12592.82, 12591.43, 12590),
189+
(9., 12., 0),
190+
(900., 1200., 0),
191+
(1900., 1200., 0),
192+
(0.99, 1.01, 1),
193+
(9.99, 10.01, 10),
194+
(99.99, 100.01, 100),
195+
(5.99, 6.01, 6),
196+
(15.99, 16.01, 16),
197+
(-0.452, 0.492, 0),
198+
(-0.492, 0.492, 0),
199+
(12331.4, 12350.5, 12300),
200+
(-12335.3, 12335.3, 0)]
201+
202+
for left, right, offset in test_data:
203+
yield check_offset_for, left, right, offset
209204

210205

211206
def _logfe_helper(formatter, base, locs, i, expected_result):

lib/matplotlib/ticker.py

+19-15
Original file line numberDiff line numberDiff line change
@@ -567,34 +567,38 @@ def _compute_offset(self):
567567
lmin, lmax = locs.min(), locs.max()
568568
# min, max comparing absolute values (we want division to round towards
569569
# zero so we work on absolute values).
570-
abs_min, abs_max = sorted(map(abs, [lmin, lmax]))
571-
# Only use offset if there are at least two ticks, every tick has the
572-
# same sign, and if the span is small compared to the absolute values.
573-
if (lmin == lmax or lmin <= 0 <= lmax or
574-
(abs_max - abs_min) / abs_max >= 1e-2):
570+
abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))])
571+
# Only use offset if there are at least two ticks and every tick has
572+
# the same sign.
573+
if lmin == lmax or lmin <= 0 <= lmax:
575574
self.offset = 0
576575
return
577576
sign = math.copysign(1, lmin)
578577
# What is the smallest power of ten such that abs_min and abs_max are
579578
# equal up to that precision?
580-
oom = 10 ** int(math.log10(abs_max) + 1)
579+
# Note: Internally using oom instead of 10 ** oom avoids some numerical
580+
# accuracy issues.
581+
oom = math.ceil(math.log10(abs_max))
581582
while True:
582-
if abs_min // oom != abs_max // oom:
583-
oom *= 10
583+
if abs_min // 10 ** oom != abs_max // 10 ** oom:
584+
oom += 1
584585
break
585-
oom /= 10
586-
if (abs_max - abs_min) / oom <= 1e-2:
586+
oom -= 1
587+
if (abs_max - abs_min) / 10 ** oom <= 1e-2:
587588
# Handle the case of straddling a multiple of a large power of ten
588589
# (relative to the span).
589590
# What is the smallest power of ten such that abs_min and abs_max
590591
# at most 1 apart?
591-
oom = 10 ** int(math.log10(abs_max) + 1)
592+
oom = math.ceil(math.log10(abs_max))
592593
while True:
593-
if abs_max // oom - abs_min // oom > 1:
594-
oom *= 10
594+
if abs_max // 10 ** oom - abs_min // 10 ** oom > 1:
595+
oom += 1
595596
break
596-
oom /= 10
597-
self.offset = sign * (abs_max // oom) * oom
597+
oom -= 1
598+
# Only use offset if it saves at least two significant digits.
599+
self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom
600+
if abs_max // 10 ** oom >= 10
601+
else 0)
598602

599603
def _set_orderOfMagnitude(self, range):
600604
# if scientific notation is to be used, find the appropriate exponent

0 commit comments

Comments
 (0)