Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit beb08c8

Browse files
committed
Merge pull request #5785 from anntzer/better-offsettext-choice
Better choice of offset-text.
1 parent f82fc3b commit beb08c8

3 files changed

Lines changed: 90 additions & 19 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Improved offset text choice
2+
---------------------------
3+
The default offset-text choice was changed to only use significant digits that
4+
are common to all ticks (e.g. 1231..1239 -> 1230, instead of 1231), except when
5+
they straddle a relatively large multiple of a power of ten, in which case that
6+
multiple is chosen (e.g. 1999..2001->2000).

lib/matplotlib/tests/test_ticker.py

Lines changed: 48 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,53 @@ 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 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+
(1, 1, 1),
184+
(123, 123, 120),
185+
# Test cases courtesy of @WeatherGod
186+
(.4538, .4578, .45),
187+
(3789.12, 3783.1, 3780),
188+
(45124.3, 45831.75, 45000),
189+
(0.000721, 0.0007243, 0.00072),
190+
(12592.82, 12591.43, 12590),
191+
(9., 12., 0),
192+
(900., 1200., 0),
193+
(1900., 1200., 0),
194+
(0.99, 1.01, 1),
195+
(9.99, 10.01, 10),
196+
(99.99, 100.01, 100),
197+
(5.99, 6.01, 6),
198+
(15.99, 16.01, 16),
199+
(-0.452, 0.492, 0),
200+
(-0.492, 0.492, 0),
201+
(12331.4, 12350.5, 12300),
202+
(-12335.3, 12335.3, 0)]
203+
204+
for left, right, offset in test_data:
205+
yield check_offset_for, left, right, offset
206+
yield check_offset_for, right, left, offset
207+
208+
162209
def _logfe_helper(formatter, base, locs, i, expected_result):
163210
vals = base**locs
164211
labels = [formatter(x, pos) for (x, pos) in zip(vals, i)]

lib/matplotlib/ticker.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
from matplotlib.externals import six
160160

161161
import decimal
162+
import itertools
162163
import locale
163164
import math
164165
import numpy as np
@@ -635,33 +636,50 @@ def set_locs(self, locs):
635636
vmin, vmax = self.axis.get_view_interval()
636637
d = abs(vmax - vmin)
637638
if self._useOffset:
638-
self._set_offset(d)
639+
self._compute_offset()
639640
self._set_orderOfMagnitude(d)
640641
self._set_format(vmin, vmax)
641642

642-
def _set_offset(self, range):
643-
# offset of 20,001 is 20,000, for example
643+
def _compute_offset(self):
644644
locs = self.locs
645-
646-
if locs is None or not len(locs) or range == 0:
645+
if locs is None or not len(locs):
647646
self.offset = 0
648647
return
648+
# Restrict to visible ticks.
649649
vmin, vmax = sorted(self.axis.get_view_interval())
650650
locs = np.asarray(locs)
651651
locs = locs[(vmin <= locs) & (locs <= vmax)]
652-
ave_loc = np.mean(locs)
653-
if len(locs) and ave_loc: # dont want to take log10(0)
654-
ave_oom = math.floor(math.log10(np.mean(np.absolute(locs))))
655-
range_oom = math.floor(math.log10(range))
656-
657-
if np.absolute(ave_oom - range_oom) >= 3: # four sig-figs
658-
p10 = 10 ** range_oom
659-
if ave_loc < 0:
660-
self.offset = (math.ceil(np.max(locs) / p10) * p10)
661-
else:
662-
self.offset = (math.floor(np.min(locs) / p10) * p10)
663-
else:
664-
self.offset = 0
652+
if not len(locs):
653+
self.offset = 0
654+
return
655+
lmin, lmax = locs.min(), locs.max()
656+
# Only use offset if there are at least two ticks and every tick has
657+
# the same sign.
658+
if lmin == lmax or lmin <= 0 <= lmax:
659+
self.offset = 0
660+
return
661+
# min, max comparing absolute values (we want division to round towards
662+
# zero so we work on absolute values).
663+
abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))])
664+
sign = math.copysign(1, lmin)
665+
# What is the smallest power of ten such that abs_min and abs_max are
666+
# equal up to that precision?
667+
# Note: Internally using oom instead of 10 ** oom avoids some numerical
668+
# accuracy issues.
669+
oom_max = math.ceil(math.log10(abs_max))
670+
oom = 1 + next(oom for oom in itertools.count(oom_max, -1)
671+
if abs_min // 10 ** oom != abs_max // 10 ** oom)
672+
if (abs_max - abs_min) / 10 ** oom <= 1e-2:
673+
# Handle the case of straddling a multiple of a large power of ten
674+
# (relative to the span).
675+
# What is the smallest power of ten such that abs_min and abs_max
676+
# are no more than 1 apart at that precision?
677+
oom = 1 + next(oom for oom in itertools.count(oom_max, -1)
678+
if abs_max // 10 ** oom - abs_min // 10 ** oom > 1)
679+
# Only use offset if it saves at least two significant digits.
680+
self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom
681+
if abs_max // 10 ** oom >= 10
682+
else 0)
665683

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

0 commit comments

Comments
 (0)