diff --git a/examples/api/skewt.py b/examples/api/skewt.py index f586fc388a4e..93891f5a2122 100644 --- a/examples/api/skewt.py +++ b/examples/api/skewt.py @@ -18,57 +18,85 @@ import matplotlib.transforms as transforms import matplotlib.axis as maxis import matplotlib.spines as mspines -import matplotlib.path as mpath from matplotlib.projections import register_projection + # The sole purpose of this class is to look at the upper, lower, or total # interval as appropriate and see what parts of the tick to draw, if any. +class SkewXTick(maxis.XTick): + def update_position(self, loc): + # This ensures that the new value of the location is set before + # any other updates take place + self._loc = loc + super(SkewXTick, self).update_position(loc) + def _has_default_loc(self): + return self.get_loc() is None -class SkewXTick(maxis.XTick): - def draw(self, renderer): - if not self.get_visible(): - return - renderer.open_group(self.__name__) + def _need_lower(self): + return (self._has_default_loc() or + transforms.interval_contains(self.axes.lower_xlim, + self.get_loc())) + + def _need_upper(self): + return (self._has_default_loc() or + transforms.interval_contains(self.axes.upper_xlim, + self.get_loc())) - lower_interval = self.axes.xaxis.lower_interval - upper_interval = self.axes.xaxis.upper_interval + @property + def gridOn(self): + return (self._gridOn and (self._has_default_loc() or + transforms.interval_contains(self.get_view_interval(), + self.get_loc()))) + + @gridOn.setter + def gridOn(self, value): + self._gridOn = value - if self.gridOn and transforms.interval_contains( - self.axes.xaxis.get_view_interval(), self.get_loc()): - self.gridline.draw(renderer) + @property + def tick1On(self): + return self._tick1On and self._need_lower() - if transforms.interval_contains(lower_interval, self.get_loc()): - if self.tick1On: - self.tick1line.draw(renderer) - if self.label1On: - self.label1.draw(renderer) + @tick1On.setter + def tick1On(self, value): + self._tick1On = value - if transforms.interval_contains(upper_interval, self.get_loc()): - if self.tick2On: - self.tick2line.draw(renderer) - if self.label2On: - self.label2.draw(renderer) + @property + def label1On(self): + return self._label1On and self._need_lower() - renderer.close_group(self.__name__) + @label1On.setter + def label1On(self, value): + self._label1On = value + + @property + def tick2On(self): + return self._tick2On and self._need_upper() + + @tick2On.setter + def tick2On(self, value): + self._tick2On = value + + @property + def label2On(self): + return self._label2On and self._need_upper() + + @label2On.setter + def label2On(self, value): + self._label2On = value + + def get_view_interval(self): + return self.axes.xaxis.get_view_interval() # This class exists to provide two separate sets of intervals to the tick, # as well as create instances of the custom tick class SkewXAxis(maxis.XAxis): - def __init__(self, *args, **kwargs): - maxis.XAxis.__init__(self, *args, **kwargs) - self.upper_interval = 0.0, 1.0 - def _get_tick(self, major): - return SkewXTick(self.axes, 0, '', major=major) - - @property - def lower_interval(self): - return self.axes.viewLim.intervalx + return SkewXTick(self.axes, None, '', major=major) def get_view_interval(self): - return self.upper_interval[0], self.axes.viewLim.intervalx[1] + return self.axes.upper_xlim[0], self.axes.lower_xlim[1] # This class exists to calculate the separate data range of the @@ -76,18 +104,11 @@ def get_view_interval(self): # to the X-axis artist for ticking and gridlines class SkewSpine(mspines.Spine): def _adjust_location(self): - trans = self.axes.transDataToAxes.inverted() + pts = self._path.vertices if self.spine_type == 'top': - yloc = 1.0 + pts[:, 0] = self.axes.upper_xlim else: - yloc = 0.0 - left = trans.transform_point((0.0, yloc))[0] - right = trans.transform_point((1.0, yloc))[0] - - pts = self._path.vertices - pts[0, 0] = left - pts[1, 0] = right - self.axis.upper_interval = (left, right) + pts[:, 0] = self.axes.lower_xlim # This class handles registration of the skew-xaxes as a projection as well @@ -143,13 +164,24 @@ def _set_lim_and_transforms(self): transforms.IdentityTransform()) + transforms.Affine2D().skew_deg(rot, 0)) + self.transAxes + @property + def lower_xlim(self): + return self.axes.viewLim.intervalx + + @property + def upper_xlim(self): + pts = [[0., 1.], [1., 1.]] + return self.transDataToAxes.inverted().transform(pts)[:, 0] + + # Now register the projection with matplotlib so the user can select # it. register_projection(SkewXAxes) if __name__ == '__main__': # Now make a simple example using the custom projection. - from matplotlib.ticker import ScalarFormatter, MultipleLocator + from matplotlib.ticker import (MultipleLocator, NullFormatter, + ScalarFormatter) import matplotlib.pyplot as plt from six import StringIO import numpy as np @@ -242,15 +274,16 @@ def _set_lim_and_transforms(self): plt.grid(True) # Plot the data using normal plotting functions, in this case using - # log scaling in Y, as dicatated by the typical meteorological plot - ax.semilogy(T, p) - ax.semilogy(Td, p) + # log scaling in Y, as dictated by the typical meteorological plot + ax.semilogy(T, p, color='C3') + ax.semilogy(Td, p, color='C2') # An example of a slanted line at constant X - l = ax.axvline(0) + l = ax.axvline(0, color='C0') # Disables the log-formatting that comes with semilogy ax.yaxis.set_major_formatter(ScalarFormatter()) + ax.yaxis.set_minor_formatter(NullFormatter()) ax.set_yticks(np.linspace(100, 1000, 10)) ax.set_ylim(1050, 100) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 1167f4161d25..cbae01d37bdb 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -344,6 +344,12 @@ def update_position(self, loc): 'Set the location of tick in data coords with scalar *loc*' raise NotImplementedError('Derived must override') + def _get_text1_transform(self): + raise NotImplementedError('Derived must override') + + def _get_text2_transform(self): + raise NotImplementedError('Derived must override') + class XTick(Tick): """ diff --git a/lib/matplotlib/tests/test_skew.py b/lib/matplotlib/tests/test_skew.py index 662bbc6cbb8c..27a175b3771d 100644 --- a/lib/matplotlib/tests/test_skew.py +++ b/lib/matplotlib/tests/test_skew.py @@ -5,18 +5,14 @@ unicode_literals) import itertools -import six -from nose.tools import assert_true -import numpy as np import matplotlib.pyplot as plt -from matplotlib.testing.decorators import cleanup, image_comparison +from matplotlib.testing.decorators import image_comparison from matplotlib.axes import Axes import matplotlib.transforms as transforms import matplotlib.axis as maxis import matplotlib.spines as mspines -import matplotlib.path as mpath import matplotlib.patches as mpatch from matplotlib.projections import register_projection @@ -24,76 +20,91 @@ # The sole purpose of this class is to look at the upper, lower, or total # interval as appropriate and see what parts of the tick to draw, if any. class SkewXTick(maxis.XTick): - def draw(self, renderer): - if not self.get_visible(): - return - renderer.open_group(self.__name__) + def update_position(self, loc): + # This ensures that the new value of the location is set before + # any other updates take place + self._loc = loc + super(SkewXTick, self).update_position(loc) - lower_interval = self.axes.xaxis.lower_interval - upper_interval = self.axes.xaxis.upper_interval + def _has_default_loc(self): + return self.get_loc() is None - if self.gridOn and transforms.interval_contains( - self.axes.xaxis.get_view_interval(), self.get_loc()): - self.gridline.draw(renderer) + def _need_lower(self): + return (self._has_default_loc() or + transforms.interval_contains(self.axes.lower_xlim, + self.get_loc())) - if transforms.interval_contains(lower_interval, self.get_loc()): - if self.tick1On: - self.tick1line.draw(renderer) - if self.label1On: - self.label1.draw(renderer) + def _need_upper(self): + return (self._has_default_loc() or + transforms.interval_contains(self.axes.upper_xlim, + self.get_loc())) - if transforms.interval_contains(upper_interval, self.get_loc()): - if self.tick2On: - self.tick2line.draw(renderer) - if self.label2On: - self.label2.draw(renderer) + @property + def gridOn(self): + return (self._gridOn and (self._has_default_loc() or + transforms.interval_contains(self.get_view_interval(), + self.get_loc()))) + + @gridOn.setter + def gridOn(self, value): + self._gridOn = value + + @property + def tick1On(self): + return self._tick1On and self._need_lower() + + @tick1On.setter + def tick1On(self, value): + self._tick1On = value + + @property + def label1On(self): + return self._label1On and self._need_lower() + + @label1On.setter + def label1On(self, value): + self._label1On = value - renderer.close_group(self.__name__) + @property + def tick2On(self): + return self._tick2On and self._need_upper() + + @tick2On.setter + def tick2On(self, value): + self._tick2On = value + + @property + def label2On(self): + return self._label2On and self._need_upper() + + @label2On.setter + def label2On(self, value): + self._label2On = value + + def get_view_interval(self): + return self.axes.xaxis.get_view_interval() # This class exists to provide two separate sets of intervals to the tick, # as well as create instances of the custom tick class SkewXAxis(maxis.XAxis): - def __init__(self, *args, **kwargs): - maxis.XAxis.__init__(self, *args, **kwargs) - self.upper_interval = 0.0, 1.0 - def _get_tick(self, major): - return SkewXTick(self.axes, 0, '', major=major) - - @property - def lower_interval(self): - return self.axes.viewLim.intervalx + return SkewXTick(self.axes, None, '', major=major) def get_view_interval(self): - return self.upper_interval[0], self.axes.viewLim.intervalx[1] + return self.axes.upper_xlim[0], self.axes.lower_xlim[1] # This class exists to calculate the separate data range of the # upper X-axis and draw the spine there. It also provides this range # to the X-axis artist for ticking and gridlines class SkewSpine(mspines.Spine): - def __init__(self, axes, spine_type): - if spine_type == 'bottom': - loc = 0.0 - else: - loc = 1.0 - mspines.Spine.__init__(self, axes, spine_type, - mpath.Path([(13, loc), (13, loc)])) - def _adjust_location(self): - trans = self.axes.transDataToAxes.inverted() + pts = self._path.vertices if self.spine_type == 'top': - yloc = 1.0 + pts[:, 0] = self.axes.upper_xlim else: - yloc = 0.0 - left = trans.transform_point((0.0, yloc))[0] - right = trans.transform_point((1.0, yloc))[0] - - pts = self._path.vertices - pts[0, 0] = left - pts[1, 0] = right - self.axis.upper_interval = (left, right) + pts[:, 0] = self.axes.lower_xlim # This class handles registration of the skew-xaxes as a projection as well @@ -106,7 +117,7 @@ class SkewXAxes(Axes): name = 'skewx' def _init_axis(self): - #Taken from Axes and modified to use our modified X-axis + # Taken from Axes and modified to use our modified X-axis self.xaxis = SkewXAxis(self) self.spines['top'].register_axis(self.xaxis) self.spines['bottom'].register_axis(self.xaxis) @@ -115,7 +126,7 @@ def _init_axis(self): self.spines['right'].register_axis(self.yaxis) def _gen_axes_spines(self): - spines = {'top': SkewSpine(self, 'top'), + spines = {'top': SkewSpine.linear_spine(self, 'top'), 'bottom': mspines.Spine.linear_spine(self, 'bottom'), 'left': mspines.Spine.linear_spine(self, 'left'), 'right': mspines.Spine.linear_spine(self, 'right')} @@ -128,7 +139,7 @@ def _set_lim_and_transforms(self): """ rot = 30 - #Get the standard transform setup from the Axes base class + # Get the standard transform setup from the Axes base class Axes._set_lim_and_transforms(self) # Need to put the skew in the middle, after the scale and limits, @@ -150,6 +161,16 @@ def _set_lim_and_transforms(self): transforms.IdentityTransform()) + transforms.Affine2D().skew_deg(rot, 0)) + self.transAxes + @property + def lower_xlim(self): + return self.axes.viewLim.intervalx + + @property + def upper_xlim(self): + pts = [[0., 1.], [1., 1.]] + return self.transDataToAxes.inverted().transform(pts)[:, 0] + + # Now register the projection with matplotlib so the user can select # it. register_projection(SkewXAxes) @@ -164,7 +185,7 @@ def test_set_line_coll_dash_image(): ax.grid(True) # An example of a slanted line at constant X - l = ax.axvline(0, color='b') + ax.axvline(0, color='b') @image_comparison(baseline_images=['skew_rects'], remove_text=True)