Skip to content

Commit

Permalink
Better choice of offset-text.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
anntzer committed Jan 27, 2016
1 parent 2a4863c commit 226b996
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 19 deletions.
51 changes: 50 additions & 1 deletion lib/matplotlib/tests/test_ticker.py
Expand Up @@ -3,7 +3,7 @@

from matplotlib.externals import six
import nose.tools
from nose.tools import assert_raises
from nose.tools import assert_equal, assert_raises
from numpy.testing import assert_almost_equal
import numpy as np
import matplotlib
Expand Down Expand Up @@ -159,6 +159,55 @@ def test_SymmetricalLogLocator_set_params():
nose.tools.assert_equal(sym.numticks, 8)


@cleanup
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 _logfe_helper(formatter, base, locs, i, expected_result):
vals = base**locs
labels = [formatter(x, pos) for (x, pos) in zip(vals, i)]
Expand Down
56 changes: 38 additions & 18 deletions lib/matplotlib/ticker.py
Expand Up @@ -548,33 +548,53 @@ def set_locs(self, locs):
vmin, vmax = self.axis.get_view_interval()
d = abs(vmax - vmin)
if self._useOffset:
self._set_offset(d)
self._compute_offset()
self._set_orderOfMagnitude(d)
self._set_format(vmin, vmax)

def _set_offset(self, range):
# offset of 20,001 is 20,000, for example
def _compute_offset(self):
locs = self.locs

if locs is None or not len(locs) or range == 0:
if locs is None or not len(locs):
self.offset = 0
return
# Restrict to visible ticks.
vmin, vmax = sorted(self.axis.get_view_interval())
locs = np.asarray(locs)
locs = locs[(vmin <= locs) & (locs <= vmax)]
ave_loc = np.mean(locs)
if len(locs) and ave_loc: # dont want to take log10(0)
ave_oom = math.floor(math.log10(np.mean(np.absolute(locs))))
range_oom = math.floor(math.log10(range))

if np.absolute(ave_oom - range_oom) >= 3: # four sig-figs
p10 = 10 ** range_oom
if ave_loc < 0:
self.offset = (math.ceil(np.max(locs) / p10) * p10)
else:
self.offset = (math.floor(np.min(locs) / p10) * p10)
else:
self.offset = 0
if not len(locs):
self.offset = 0
return
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):
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)
while True:
if abs_min // oom != abs_max // oom:
oom *= 10
break
oom /= 10
if (abs_max - abs_min) / 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)
while True:
if abs_max // oom - abs_min // oom > 1:
oom *= 10
break
oom /= 10
self.offset = sign * (abs_max // oom) * oom

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

0 comments on commit 226b996

Please sign in to comment.