Skip to content

Commit 9a4ecfb

Browse files
committed
Better choice of offset-text.
The axis offset text is chosen as follows: xlims => offsettext 123, 189 => 0 12341, 12349 => 12340 99999.5, 100010.5 => 100000 # (also a test for matplotlib#5780) 99990.5, 100000.5 => 100000 1233999, 1234001 => 1234000 (and the same for negative limits). See matplotlib#5755.
1 parent 3b8c094 commit 9a4ecfb

2 files changed

Lines changed: 88 additions & 19 deletions

File tree

‎lib/matplotlib/tests/test_ticker.py‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from matplotlib.externals import six
55
import nose.tools
6-
from nose.tools import assert_raises
6+
from nose.tools import assert_equal, assert_raises
77
from numpy.testing import assert_almost_equal
88
import numpy as np
99
import matplotlib
@@ -159,6 +159,55 @@ def test_SymmetricalLogLocator_set_params():
159159
nose.tools.assert_equal(sym.numticks, 8)
160160

161161

162+
@cleanup
163+
def test_ScalarFormatter_offset_value():
164+
fig, ax = plt.subplots()
165+
formatter = ax.get_xaxis().get_major_formatter()
166+
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)
209+
210+
162211
def _logfe_helper(formatter, base, locs, i, expected_result):
163212
vals = base**locs
164213
labels = [formatter(x, pos) for (x, pos) in zip(vals, i)]

‎lib/matplotlib/ticker.py‎

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -663,33 +663,53 @@ def set_locs(self, locs):
663663
vmin, vmax = self.axis.get_view_interval()
664664
d = abs(vmax - vmin)
665665
if self._useOffset:
666-
self._set_offset(d)
666+
self._compute_offset()
667667
self._set_orderOfMagnitude(d)
668668
self._set_format(vmin, vmax)
669669

670-
def _set_offset(self, range):
671-
# offset of 20,001 is 20,000, for example
670+
def _compute_offset(self):
672671
locs = self.locs
673-
674-
if locs is None or not len(locs) or range == 0:
672+
if locs is None or not len(locs):
675673
self.offset = 0
676674
return
675+
# Restrict to visible ticks.
677676
vmin, vmax = sorted(self.axis.get_view_interval())
678677
locs = np.asarray(locs)
679678
locs = locs[(vmin <= locs) & (locs <= vmax)]
680-
ave_loc = np.mean(locs)
681-
if len(locs) and ave_loc: # dont want to take log10(0)
682-
ave_oom = math.floor(math.log10(np.mean(np.absolute(locs))))
683-
range_oom = math.floor(math.log10(range))
684-
685-
if np.absolute(ave_oom - range_oom) >= 3: # four sig-figs
686-
p10 = 10 ** range_oom
687-
if ave_loc < 0:
688-
self.offset = (math.ceil(np.max(locs) / p10) * p10)
689-
else:
690-
self.offset = (math.floor(np.min(locs) / p10) * p10)
691-
else:
692-
self.offset = 0
679+
if not len(locs):
680+
self.offset = 0
681+
return
682+
lmin, lmax = locs.min(), locs.max()
683+
# min, max comparing absolute values (we want division to round towards
684+
# zero so we work on absolute values).
685+
abs_min, abs_max = sorted(map(abs, [lmin, lmax]))
686+
# Only use offset if there are at least two ticks, every tick has the
687+
# same sign, and if the span is small compared to the absolute values.
688+
if (lmin == lmax or lmin <= 0 <= lmax or
689+
(abs_max - abs_min) / abs_max >= 1e-2):
690+
self.offset = 0
691+
return
692+
sign = math.copysign(1, lmin)
693+
# What is the smallest power of ten such that abs_min and abs_max are
694+
# equal up to that precision?
695+
oom = 10 ** int(math.log10(abs_max) + 1)
696+
while True:
697+
if abs_min // oom != abs_max // oom:
698+
oom *= 10
699+
break
700+
oom /= 10
701+
if (abs_max - abs_min) / oom <= 1e-2:
702+
# Handle the case of straddling a multiple of a large power of ten
703+
# (relative to the span).
704+
# What is the smallest power of ten such that abs_min and abs_max
705+
# at most 1 apart?
706+
oom = 10 ** int(math.log10(abs_max) + 1)
707+
while True:
708+
if abs_max // oom - abs_min // oom > 1:
709+
oom *= 10
710+
break
711+
oom /= 10
712+
self.offset = sign * (abs_max // oom) * oom
693713

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

0 commit comments

Comments
 (0)