123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- """
- Testing that skewed axes properly work.
- """
- from contextlib import ExitStack
- import itertools
- import matplotlib.pyplot as plt
- 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.patches as mpatch
- 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 draw(self, renderer):
- with ExitStack() as stack:
- for artist in [self.gridline, self.tick1line, self.tick2line,
- self.label1, self.label2]:
- stack.callback(artist.set_visible, artist.get_visible())
- needs_lower = transforms.interval_contains(
- self.axes.lower_xlim, self.get_loc())
- needs_upper = transforms.interval_contains(
- self.axes.upper_xlim, self.get_loc())
- self.tick1line.set_visible(
- self.tick1line.get_visible() and needs_lower)
- self.label1.set_visible(
- self.label1.get_visible() and needs_lower)
- self.tick2line.set_visible(
- self.tick2line.get_visible() and needs_upper)
- self.label2.set_visible(
- self.label2.get_visible() and needs_upper)
- super(SkewXTick, self).draw(renderer)
- 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 _get_tick(self, major):
- return SkewXTick(self.axes, None, '', major=major)
- def get_view_interval(self):
- 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 _adjust_location(self):
- pts = self._path.vertices
- if self.spine_type == 'top':
- pts[:, 0] = self.axes.upper_xlim
- else:
- pts[:, 0] = self.axes.lower_xlim
- # This class handles registration of the skew-xaxes as a projection as well
- # as setting up the appropriate transformations. It also overrides standard
- # spines and axes instances as appropriate.
- class SkewXAxes(Axes):
- # The projection must specify a name. This will be used be the
- # user to select the projection, i.e. ``subplot(111,
- # projection='skewx')``.
- name = 'skewx'
- def _init_axis(self):
- # 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)
- self.yaxis = maxis.YAxis(self)
- self.spines['left'].register_axis(self.yaxis)
- self.spines['right'].register_axis(self.yaxis)
- def _gen_axes_spines(self):
- 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')}
- return spines
- def _set_lim_and_transforms(self):
- """
- This is called once when the plot is created to set up all the
- transforms for the data, text and grids.
- """
- rot = 30
- # 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,
- # but before the transAxes. This way, the skew is done in Axes
- # coordinates thus performing the transform around the proper origin
- # We keep the pre-transAxes transform around for other users, like the
- # spines for finding bounds
- self.transDataToAxes = (self.transScale +
- (self.transLimits +
- transforms.Affine2D().skew_deg(rot, 0)))
- # Create the full transform from Data to Pixels
- self.transData = self.transDataToAxes + self.transAxes
- # Blended transforms like this need to have the skewing applied using
- # both axes, in axes coords like before.
- self._xaxis_transform = (transforms.blended_transform_factory(
- self.transScale + self.transLimits,
- 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)
- @image_comparison(['skew_axes'], remove_text=True)
- def test_set_line_coll_dash_image():
- fig = plt.figure()
- ax = fig.add_subplot(1, 1, 1, projection='skewx')
- ax.set_xlim(-50, 50)
- ax.set_ylim(50, -50)
- ax.grid(True)
- # An example of a slanted line at constant X
- ax.axvline(0, color='b')
- @image_comparison(['skew_rects'], remove_text=True)
- def test_skew_rectangle():
- fix, axes = plt.subplots(5, 5, sharex=True, sharey=True, figsize=(8, 8))
- axes = axes.flat
- rotations = list(itertools.product([-3, -1, 0, 1, 3], repeat=2))
- axes[0].set_xlim([-3, 3])
- axes[0].set_ylim([-3, 3])
- axes[0].set_aspect('equal', share=True)
- for ax, (xrots, yrots) in zip(axes, rotations):
- xdeg, ydeg = 45 * xrots, 45 * yrots
- t = transforms.Affine2D().skew_deg(xdeg, ydeg)
- ax.set_title('Skew of {0} in X and {1} in Y'.format(xdeg, ydeg))
- ax.add_patch(mpatch.Rectangle([-1, -1], 2, 2,
- transform=t + ax.transData,
- alpha=0.5, facecolor='coral'))
- plt.subplots_adjust(wspace=0, left=0.01, right=0.99, bottom=0.01, top=0.99)
|