|
16 | 16 | import matplotlib.transforms as mtransforms
|
17 | 17 | import matplotlib.units as munits
|
18 | 18 | import numpy as np
|
| 19 | +import warnings |
19 | 20 |
|
20 | 21 | GRIDLINE_INTERPOLATION_STEPS = 180
|
21 | 22 |
|
@@ -972,11 +973,36 @@ def _update_ticks(self, renderer):
|
972 | 973 | tick_tups = [ti for ti in tick_tups
|
973 | 974 | if (ti[1] >= ilow) and (ti[1] <= ihigh)]
|
974 | 975 |
|
| 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 | + |
975 | 1001 | ticks_to_draw = []
|
976 | 1002 | for tick, loc, label in tick_tups:
|
977 | 1003 | if tick is None:
|
978 | 1004 | continue
|
979 |
| - if not mtransforms.interval_contains(interval, loc): |
| 1005 | + if not mtransforms.interval_contains(interval_expanded, loc): |
980 | 1006 | continue
|
981 | 1007 | tick.update_position(loc)
|
982 | 1008 | tick.set_label1(label)
|
@@ -1599,6 +1625,35 @@ def _get_offset_text(self):
|
1599 | 1625 | self.offset_text_position = 'bottom'
|
1600 | 1626 | return offsetText
|
1601 | 1627 |
|
| 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 | + |
1602 | 1657 | def get_label_position(self):
|
1603 | 1658 | """
|
1604 | 1659 | Return the label position (top or bottom)
|
@@ -1874,6 +1929,27 @@ def _get_offset_text(self):
|
1874 | 1929 | self.offset_text_position = 'left'
|
1875 | 1930 | return offsetText
|
1876 | 1931 |
|
| 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 | + |
1877 | 1953 | def get_label_position(self):
|
1878 | 1954 | """
|
1879 | 1955 | Return the label position (left or right)
|
|
0 commit comments