Skip to content

Commit 6c2089d

Browse files
committed
Only use offset if it saves >=2 sig. digits.
Tests cases courtesy of @WeatherGod. Slightly improved numerical accuracy.
1 parent 56026ae commit 6c2089d

File tree

2 files changed

+56
-58
lines changed

2 files changed

+56
-58
lines changed

lib/matplotlib/tests/test_ticker.py

+37-43
Original file line numberDiff line numberDiff line change
@@ -159,53 +159,47 @@ def test_SymmetricalLogLocator_set_params():
159159
nose.tools.assert_equal(sym.numticks, 8)
160160

161161

162-
@cleanup
163162
def test_ScalarFormatter_offset_value():
164163
fig, ax = plt.subplots()
165164
formatter = ax.get_xaxis().get_major_formatter()
166165

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

210204

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

lib/matplotlib/ticker.py

+19-15
Original file line numberDiff line numberDiff line change
@@ -564,34 +564,38 @@ def _compute_offset(self):
564564
lmin, lmax = locs.min(), locs.max()
565565
# min, max comparing absolute values (we want division to round towards
566566
# zero so we work on absolute values).
567-
abs_min, abs_max = sorted(map(abs, [lmin, lmax]))
568-
# Only use offset if there are at least two ticks, every tick has the
569-
# same sign, and if the span is small compared to the absolute values.
570-
if (lmin == lmax or lmin <= 0 <= lmax or
571-
(abs_max - abs_min) / abs_max >= 1e-2):
567+
abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))])
568+
# Only use offset if there are at least two ticks and every tick has
569+
# the same sign.
570+
if lmin == lmax or lmin <= 0 <= lmax:
572571
self.offset = 0
573572
return
574573
sign = math.copysign(1, lmin)
575574
# What is the smallest power of ten such that abs_min and abs_max are
576575
# equal up to that precision?
577-
oom = 10 ** int(math.log10(abs_max) + 1)
576+
# Note: Internally using oom instead of 10 ** oom avoids some numerical
577+
# accuracy issues.
578+
oom = math.ceil(math.log10(abs_max))
578579
while True:
579-
if abs_min // oom != abs_max // oom:
580-
oom *= 10
580+
if abs_min // 10 ** oom != abs_max // 10 ** oom:
581+
oom += 1
581582
break
582-
oom /= 10
583-
if (abs_max - abs_min) / oom <= 1e-2:
583+
oom -= 1
584+
if (abs_max - abs_min) / 10 ** oom <= 1e-2:
584585
# Handle the case of straddling a multiple of a large power of ten
585586
# (relative to the span).
586587
# What is the smallest power of ten such that abs_min and abs_max
587588
# at most 1 apart?
588-
oom = 10 ** int(math.log10(abs_max) + 1)
589+
oom = math.ceil(math.log10(abs_max))
589590
while True:
590-
if abs_max // oom - abs_min // oom > 1:
591-
oom *= 10
591+
if abs_max // 10 ** oom - abs_min // 10 ** oom > 1:
592+
oom += 1
592593
break
593-
oom /= 10
594-
self.offset = sign * (abs_max // oom) * oom
594+
oom -= 1
595+
# Only use offset if it saves at least two significant digits.
596+
self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom
597+
if abs_max // 10 ** oom >= 10
598+
else 0)
595599

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

0 commit comments

Comments
 (0)