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

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
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 #5780)
99990.5, 100000.5 => 100000
1233999, 1234001 => 1234000

(and the same for negative limits).

See #5755.
  • Loading branch information
anntzer committed May 2, 2016
commit 9a4ecfbdf235851a127f22152841631dadbc7558
51 changes: 50 additions & 1 deletion lib/matplotlib/tests/test_ticker.py
Original file line number Diff line number Diff line change
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():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs an @cleanup decorator

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it is, the @cleanup decorator doesn't support generative tests. I'll write an patch for that first.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. That is something that has driven me crazy a couple of times. I have dealt with it by putting the decorator on the called function and creating all of the figures/axes in that function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #5809.

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
Original file line number Diff line number Diff line change
Expand Up @@ -663,33 +663,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):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fully agree that renaming this method was really needed; the new name actually reflects the purpose of the function.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contra my reluctance to change the public API at all, changing anything with a leading _ is fair game.

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/at/are at/

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