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

Skip to content

Commit 1aa61e3

Browse files
committed
Merge pull request matplotlib#1686 from dhyams/fix_lost_ticks
Fix lost ticks
2 parents 4af901c + 3cf7ea5 commit 1aa61e3

File tree

3 files changed

+84
-8
lines changed

3 files changed

+84
-8
lines changed

lib/matplotlib/axis.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import matplotlib.transforms as mtransforms
1717
import matplotlib.units as munits
1818
import numpy as np
19+
import warnings
1920

2021
GRIDLINE_INTERPOLATION_STEPS = 180
2122

@@ -972,11 +973,36 @@ def _update_ticks(self, renderer):
972973
tick_tups = [ti for ti in tick_tups
973974
if (ti[1] >= ilow) and (ti[1] <= ihigh)]
974975

976+
# so that we don't lose ticks on the end, expand out the interval ever so slightly. The
977+
# "ever so slightly" is defined to be the width of a half of a pixel. We don't want to draw
978+
# a tick that even one pixel outside of the defined axis interval.
979+
if interval[0] <= interval[1]:
980+
interval_expanded = interval
981+
else:
982+
interval_expanded = interval[1], interval[0]
983+
984+
if hasattr(self, '_get_pixel_distance_along_axis'):
985+
# normally, one does not want to catch all exceptions that could possibly happen, but it
986+
# is not clear exactly what exceptions might arise from a user's projection (their rendition
987+
# of the Axis object). So, we catch all, with the idea that one would rather potentially
988+
# lose a tick from one side of the axis or another, rather than see a stack trace.
989+
try:
990+
ds1 = self._get_pixel_distance_along_axis(interval_expanded[0], -0.5)
991+
except:
992+
warnings.warn("Unable to find pixel distance along axis for interval padding; assuming no interval padding needed.")
993+
ds1 = 0.0
994+
try:
995+
ds2 = self._get_pixel_distance_along_axis(interval_expanded[1], +0.5)
996+
except:
997+
warnings.warn("Unable to find pixel distance along axis for interval padding; assuming no interval padding needed.")
998+
ds2 = 0.0
999+
interval_expanded = (interval[0] - ds1, interval[1] + ds2)
1000+
9751001
ticks_to_draw = []
9761002
for tick, loc, label in tick_tups:
9771003
if tick is None:
9781004
continue
979-
if not mtransforms.interval_contains(interval, loc):
1005+
if not mtransforms.interval_contains(interval_expanded, loc):
9801006
continue
9811007
tick.update_position(loc)
9821008
tick.set_label1(label)
@@ -1599,6 +1625,35 @@ def _get_offset_text(self):
15991625
self.offset_text_position = 'bottom'
16001626
return offsetText
16011627

1628+
def _get_pixel_distance_along_axis(self, where, perturb):
1629+
"""
1630+
Returns the amount, in data coordinates, that a single pixel corresponds to in the
1631+
locality given by "where", which is also given in data coordinates, and is an x coordinate.
1632+
"perturb" is the amount to perturb the pixel. Usually +0.5 or -0.5.
1633+
1634+
Implementing this routine for an axis is optional; if present, it will ensure that no
1635+
ticks are lost due to round-off at the extreme ends of an axis.
1636+
"""
1637+
1638+
# Note that this routine does not work for a polar axis, because of the 1e-10 below. To
1639+
# do things correctly, we need to use rmax instead of 1e-10 for a polar axis. But
1640+
# since we do not have that kind of information at this point, we just don't try to
1641+
# pad anything for the theta axis of a polar plot.
1642+
if self.axes.name == 'polar':
1643+
return 0.0
1644+
1645+
#
1646+
# first figure out the pixel location of the "where" point. We use 1e-10 for the
1647+
# y point, so that we remain compatible with log axes.
1648+
#
1649+
trans = self.axes.transData # transformation from data coords to display coords
1650+
transinv = trans.inverted() # transformation from display coords to data coords
1651+
pix = trans.transform_point((where, 1e-10))
1652+
ptp = transinv.transform_point((pix[0] + perturb, pix[1])) # perturb the pixel.
1653+
dx = abs(ptp[0] - where)
1654+
1655+
return dx
1656+
16021657
def get_label_position(self):
16031658
"""
16041659
Return the label position (top or bottom)
@@ -1874,6 +1929,27 @@ def _get_offset_text(self):
18741929
self.offset_text_position = 'left'
18751930
return offsetText
18761931

1932+
def _get_pixel_distance_along_axis(self, where, perturb):
1933+
"""
1934+
Returns the amount, in data coordinates, that a single pixel corresponds to in the
1935+
locality given by "where", which is also given in data coordinates, and is an y coordinate.
1936+
"perturb" is the amount to perturb the pixel. Usually +0.5 or -0.5.
1937+
1938+
Implementing this routine for an axis is optional; if present, it will ensure that no
1939+
ticks are lost due to round-off at the extreme ends of an axis.
1940+
"""
1941+
1942+
#
1943+
# first figure out the pixel location of the "where" point. We use 1e-10 for the
1944+
# x point, so that we remain compatible with log axes.
1945+
#
1946+
trans = self.axes.transData # transformation from data coords to display coords
1947+
transinv = trans.inverted() # transformation from display coords to data coords
1948+
pix = trans.transform_point((1e-10, where))
1949+
ptp = transinv.transform_point((pix[0], pix[1] + perturb)) # perturb the pixel.
1950+
dy = abs(ptp[1] - where)
1951+
return dy
1952+
18771953
def get_label_position(self):
18781954
"""
18791955
Return the label position (left or right)

lib/matplotlib/tests/test_ticker.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def test_LinearLocator():
2626

2727
def test_MultipleLocator():
2828
loc = mticker.MultipleLocator(base=3.147)
29-
test_value = np.array([-6.294, -3.147, 0., 3.147, 6.294, 9.441])
29+
test_value = np.array([-9.441, -6.294, -3.147, 0., 3.147, 6.294, 9.441, 12.588])
3030
assert_almost_equal(loc.tick_values(-7, 10), test_value)
3131

3232

@@ -35,12 +35,12 @@ def test_LogLocator():
3535

3636
assert_raises(ValueError, loc.tick_values, 0, 1000)
3737

38-
test_value = np.array([1.00000000e-03, 1.00000000e-01, 1.00000000e+01,
39-
1.00000000e+03, 1.00000000e+05, 1.00000000e+07])
38+
test_value = np.array([1.00000000e-05, 1.00000000e-03, 1.00000000e-01, 1.00000000e+01,
39+
1.00000000e+03, 1.00000000e+05, 1.00000000e+07, 1.000000000e+09])
4040
assert_almost_equal(loc.tick_values(0.001, 1.1e5), test_value)
4141

4242
loc = mticker.LogLocator(base=2)
43-
test_value = np.array([1., 2., 4., 8., 16., 32., 64., 128.])
43+
test_value = np.array([0.5, 1., 2., 4., 8., 16., 32., 64., 128., 256.])
4444
assert_almost_equal(loc.tick_values(1, 100), test_value)
4545

4646

lib/matplotlib/ticker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,7 @@ def tick_values(self, vmin, vmax):
11651165
vmin = self._base.ge(vmin)
11661166
base = self._base.get_base()
11671167
n = (vmax - vmin + 0.001 * base) // base
1168-
locs = vmin + np.arange(n + 1) * base
1168+
locs = vmin - base + np.arange(n + 3) * base
11691169
return self.raise_if_exceeds(locs)
11701170

11711171
def view_limits(self, dmin, dmax):
@@ -1450,8 +1450,8 @@ def tick_values(self, vmin, vmax):
14501450
while numdec / stride + 1 > self.numticks:
14511451
stride += 1
14521452

1453-
decades = np.arange(math.floor(vmin),
1454-
math.ceil(vmax) + stride, stride)
1453+
decades = np.arange(math.floor(vmin) - stride,
1454+
math.ceil(vmax) + 2 * stride, stride)
14551455
if hasattr(self, '_transform'):
14561456
ticklocs = self._transform.inverted().transform(decades)
14571457
if len(subs) > 1 or (len(subs == 1) and subs[0] != 1.0):

0 commit comments

Comments
 (0)