You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			171 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			171 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
Testing that skewed Axes properly work.
 | 
						|
"""
 | 
						|
 | 
						|
from contextlib import ExitStack
 | 
						|
import itertools
 | 
						|
import platform
 | 
						|
 | 
						|
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().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(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
 | 
						|
        super()._set_lim_and_transforms()
 | 
						|
 | 
						|
        # 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.png'], 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.png'], remove_text=True,
 | 
						|
                  tol=0 if platform.machine() == 'x86_64' else 0.009)
 | 
						|
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(f'Skew of {xdeg} in X and {ydeg} in Y')
 | 
						|
        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)
 |