Source code for pycalphad.plot.triangular

from matplotlib.axes import Axes
from matplotlib.patches import Polygon
from matplotlib.ticker import NullLocator, Formatter, FixedLocator
from matplotlib.transforms import Affine2D, BboxTransformTo, IdentityTransform
from matplotlib.projections import register_projection
import matplotlib.spines as mspines
import matplotlib.axis as maxis

import numpy as np

[docs]class TriangularAxes(Axes): """ A custom class for triangular projections. """ name = 'triangular' def __init__(self, *args, **kwargs): Axes.__init__(self, *args, **kwargs) self.set_aspect(1, adjustable='box', anchor='SW') self.cla() def _init_axis(self): self.xaxis = maxis.XAxis(self) self.yaxis = maxis.YAxis(self) self._update_transScale()
[docs] def cla(self): """ Override to set up some reasonable defaults. """ # Don't forget to call the base class Axes.cla(self) x_min = 0 y_min = 0 x_max = 1 y_max = 1 x_spacing = 0.1 y_spacing = 0.1 self.xaxis.set_minor_locator(NullLocator()) self.yaxis.set_minor_locator(NullLocator()) self.xaxis.set_ticks_position('bottom') self.yaxis.set_ticks_position('left') Axes.set_xlim(self, x_min, x_max) Axes.set_ylim(self, y_min, y_max) self.xaxis.set_ticks(np.arange(x_min, x_max+x_spacing, x_spacing)) self.yaxis.set_ticks(np.arange(y_min, y_max+y_spacing, y_spacing))
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. """ # There are three important coordinate spaces going on here: # # 1. Data space: The space of the data itself # # 2. Axes space: The unit rectangle (0, 0) to (1, 1) # covering the entire plot area. # # 3. Display space: The coordinates of the resulting image, # often in pixels or dpi/inch. # This function makes heavy use of the Transform classes in # ``lib/matplotlib/transforms.py.`` For more information, see # the inline documentation there. # The goal of the first two transformations is to get from the # data space (in this case longitude and latitude) to axes # space. It is separated into a non-affine and affine part so # that the non-affine part does not have to be recomputed when # a simple affine change to the figure has been made (such as # resizing the window or changing the dpi). # 1) The core transformation from data space into # rectilinear space defined in the HammerTransform class. self.transProjection = IdentityTransform() # 2) The above has an output range that is not in the unit # rectangle, so scale and translate it so it fits correctly # within the axes. The peculiar calculations of xscale and # yscale are specific to a Aitoff-Hammer projection, so don't # worry about them too much. self.transAffine = Affine2D.from_values( 1., 0, 0.5, np.sqrt(3)/2., 0, 0) self.transAffinedep = Affine2D.from_values( 1., 0, -0.5, np.sqrt(3)/2., 0, 0) #self.transAffine = IdentityTransform() # 3) This is the transformation from axes space to display # space. self.transAxes = BboxTransformTo(self.bbox) # Now put these 3 transforms together -- from data all the way # to display coordinates. Using the '+' operator, these # transforms will be applied "in order". The transforms are # automatically simplified, if possible, by the underlying # transformation framework. self.transData = \ self.transProjection + \ self.transAffine + \ self.transAxes # The main data transformation is set up. Now deal with # gridlines and tick labels. # Longitude gridlines and ticklabels. The input to these # transforms are in display space in x and axes space in y. # Therefore, the input values will be in range (-xmin, 0), # (xmax, 1). The goal of these transforms is to go from that # space to display space. The tick labels will be offset 4 # pixels from the equator. self._xaxis_pretransform = IdentityTransform() self._xaxis_transform = \ self._xaxis_pretransform + \ self.transData self._xaxis_text1_transform = \ Affine2D().scale(1.0, 0.0) + \ self.transData + \ Affine2D().translate(0.0, -20.0) self._xaxis_text2_transform = \ Affine2D().scale(1.0, 0.0) + \ self.transData + \ Affine2D().translate(0.0, -4.0) # Now set up the transforms for the latitude ticks. The input to # these transforms are in axes space in x and display space in # y. Therefore, the input values will be in range (0, -ymin), # (1, ymax). The goal of these transforms is to go from that # space to display space. The tick labels will be offset 4 # pixels from the edge of the axes ellipse. self._yaxis_transform = self.transData yaxis_text_base = \ self.transProjection + \ (self.transAffine + \ self.transAxes) self._yaxis_text1_transform = \ yaxis_text_base + \ Affine2D().translate(-8.0, 0.0) self._yaxis_text2_transform = \ yaxis_text_base + \ Affine2D().translate(8.0, 0.0)
[docs] def get_xaxis_transform(self,which='grid'): assert which in ['tick1','tick2','grid'] return self._xaxis_transform
[docs] def get_xaxis_text1_transform(self, pad): return self._xaxis_text1_transform, 'bottom', 'center'
[docs] def get_xaxis_text2_transform(self, pad): return self._xaxis_text2_transform, 'top', 'center'
[docs] def get_yaxis_transform(self,which='grid'): assert which in ['tick1','tick2','grid'] return self._yaxis_transform
[docs] def get_yaxis_text1_transform(self, pad): return self._yaxis_text1_transform, 'center', 'right'
[docs] def get_yaxis_text2_transform(self, pad): return self._yaxis_text2_transform, 'center', 'left'
def _gen_axes_spines(self): dep_spine = mspines.Spine.linear_spine(self, 'right') # Fix dependent axis to be transformed the correct way dep_spine.set_transform(self.transAffinedep + self.transAxes) return {'left':mspines.Spine.linear_spine(self, 'left'), 'bottom':mspines.Spine.linear_spine(self, 'bottom'), 'right':dep_spine} def _gen_axes_patch(self): """ Override this method to define the shape that is used for the background of the plot. It should be a subclass of Patch. Any data and gridlines will be clipped to this shape. """ return Polygon([[0,0], [0.5,np.sqrt(3)/2], [1,0]], closed=True) # Interactive panning and zooming is not supported with this projection, # so we override all of the following methods to disable it.
[docs] def can_zoom(self): """ Return True if this axes support the zoom box """ return False
[docs] def start_pan(self, x, y, button): pass
[docs] def end_pan(self): pass
[docs] def drag_pan(self, button, key, x, y): pass
# Now register the projection with matplotlib so the user can select it. register_projection(TriangularAxes)