Source code for kapteyn.rulers

#!/usr/bin/env python
#----------------------------------------------------------------------
# FILE:    rulers.py
# PURPOSE: Provide methods to plot a ruler showing great circle offsets
#          with respect to a given starting point.
# AUTHOR:  M.G.R. Vogelaar, University of Groningen, The Netherlands
# DATE:    April 17, 2010
# UPDATE:  April 17, 2010
#
# VERSION: 1.0
#
# (C) University of Groningen
# Kapteyn Astronomical Institute
# Groningen, The Netherlands
# E: gipsy@astro.rug.nl
#
#----------------------------------------------------------------------
"""
.. highlight:: python
   :linenothreshold: 1000

Module rulers
===============
This module defines a class for drawing rulers.

.. autoclass:: Ruler

"""

import numpy
from kapteyn.positions import str2pos, unitfactor

def isinside(x, y, pxlim, pylim):
   if pxlim[0] <= pxlim[1]:
      if x < pxlim[0]-0.5 or x > pxlim[1]+0.5:
         return False
   else:
      if x < pxlim[1]-0.5 or x > pxlim[0]+0.5:
         return False
   if pylim[0] <= pylim[1]:
      if y < pylim[0]-0.5 or y > pylim[1]+0.5:
         return False
   else:
      if y < pylim[1]-0.5 or y > pylim[0]+0.5:
         return False
   return True


def dispcoord(longitude, latitude, disp, direction, angle):
   #--------------------------------------------------------------------
   """
   Find a world coordinate with distance 'disp' w.r.t. given
   long, lat. The angle of the line between the two points
   has angle 'angle' w.r.t. the North.

   Note that this is a copy of a routine in maputils.
   To avoid circular imports, we copied the function here.
   
   INPUT:   longitude: numpy array, enter in degrees.
            latitude:  numpy array, enter in degrees.
            disp:      the displacement in the sky entered
                       in degrees. The value can also be
                       negative to indicate the opposite
                       direction
            angle:     the angle wrt. a great circle of
                       constant declination entered in
                       degrees.
            direction: If the longitude increases in the -X
                       direction (e.q. RA-DEC) then direction
                       is -1. else direction = +1
   """
   #--------------------------------------------------------------------
   Pi = numpy.pi
   b = abs(disp*Pi/180.0)
   a1 = longitude * Pi/180.0
   d1 = latitude * Pi/180.0
   alpha = angle * Pi/180.0
   d2 = numpy.arcsin( numpy.cos(b)*numpy.sin(d1)+numpy.cos(d1)*numpy.sin(b)*numpy.cos(alpha) )
   cosa2a1 = (numpy.cos(b) - numpy.sin(d1)*numpy.sin(d2))/(numpy.cos(d1)*numpy.cos(d2))
   sina2a1 = numpy.sin(b)*numpy.sin(alpha)/numpy.cos(d2)
   dH =  numpy.arctan2(direction*sina2a1, cosa2a1)

   a2 = a1 - dH
   lonout = a2*180.0/Pi
   latout = d2*180.0/Pi

   return lonout, latout


[docs]class Ruler(object): #----------------------------------------------------------------- """ Draws a line between two spatial positions from a start point (x1,y1) to an end point (x2,y2) with labels indicating a constant offset in world coordinates. The positions are either in pixels or in world coordinates. The start and end point can also be positions entered as a string which follows the syntax described in method :func:`kapteyn.positions.str2pos`. The ruler can also be given as a start point and a size and angle. These are distance and angle on a sphere. The ruler is a straight line but the ticks are usually not equidistant because projection effects make the offsets non linear (e.g. the TAN projection diverges while the CAR projection shows equidistant ticks). By default, the zero point is exactly in the middle of the ruler but this can be changed by setting a value for *lambda0*. The step size for the ruler ticks in units of the spatial axes is entered in parameter *step*. At least one of the axes in the plot needs to be a spatial axis. Size and step size can be entered in units given by a parameter *units*. The default unit is degrees. :param projection: The Projection object which sets the WCS for the ruler. :type projection: A :class:`wcs.Projection` object :param mixpix: The pixel of the missing spatial axis in a Position-Velocity image. :type mixpix: Integer :param pxlim: Limit in pixel coordinates for the x-axis. :type pxlim: Tuple or list with two integers. :param pylim: Limit in pixel coordinates for the y-axis. :type pylim: Tuple or list with two integers. :param aspectratio: The aspect ratio is defined as *pixel height / pixel width*. The value is needed to draw tick mark perpendicular to the ruler line for images where the pixels are not square in world coordinates. Its default is 1.0. :type aspectratio: Float :param pos1: Position information for the start point. This info overrules the values in x1 and y1. :type pos1: String :param pos2: Position information for the end point. This info overrules the values in x2 and y2. :type pos2: String :param rulersize: Instead of entering a start- and an end point, one can also enter a start point in *pos1* or in *x1, y1* and specify a size of the ruler. The size is entered in units given by parameter *units*. If no units are given, the size is in degrees. Note that with size we mean the distance on a sphere. To calculate the end point, we need an angle. this angle is given in *rulerangle*. If *rulersize* has a value, then values in *pos2* and *x2,y2* are ignored. :type rulersize: Floating point number :param rulerangle: An angel in degrees which, together with *rulersize*, sets the end point of the ruler. The angle is defined as an angle on a sphere. The angle is an astronomical angle (defined with respect to the direction of the North). :type rulerangle: Floating point number :param x1: X-location of start of ruler either in pixels or world coordinates Default is lowest pixel coordinate in x. :type x1: None or Floating point number :param y1: Y-location of start of ruler either in pixels or world coordinates Default is lowest pixel coordinate in y. :type y1: None or Floating point number :param x2: X-location of end of ruler either in pixels or world coordinates Default is highest pixel coordinate in x. :type x2: None or Floating point number :param y2: Y-location of end of ruler either in pixels or world coordinates Default is highest pixel coordinate in y. :type y2: None or Floating point number :param lambda0: Set the position of label which represents offset 0.0. Default is lambda=0.5 which represents the middle of the ruler. If you set lambda=0 then offset 0.0 is located at the start of the ruler. If you set lambda=1 then offset 0.0 is located at the end of the ruler. :type lambda0: Floating point number :param step: Step size of world coordinates in degrees or in units entered in *units*. :type step: Floating point number :param world: Set ruler mode to world coordinates (default is pixels) :type world: Boolean :param angle: Set angle of tick marks in degrees. If omitted then a default is calculated (perpendicular to ruler line) which applies to all labels. :type angle: Floating point number :param addangle: Add a constant angle in degrees to *angle*. Only useful if *angle* has its default value. This parameter is used to improve layout. :type adangle: Floating point number :param fmt: Format of the labels. See example. Do not use TeX's $ characters because these will be appended when processing the labels. :type fmt: String :param fun: Format ruler values according to this function (e.g. to convert degrees into arcminutes). The output is always in degrees. :type fun: Python function or Lambda expression :param units: Rulers ticks are labeled in a unit that is compatible with degrees. The units are set by the step size used to calculate the position of the tick marks. You can set these units explicitely with this parameter. Note that values for *fun* and *fmt* cannot be set because these are set automatically if *units* has a value. Note that *units* needs only a part of a complete units string because a case insensitive minimal match is applied. Usually one will use something like *units=arcmin* or *units=Arcsec*. Note: If a value for *units* is entered, then this method expects the step size is given in the same units. :type units: String :param fliplabelside: Choose other side of ruler to draw labels. :type fliplabelside: Boolean :param mscale: A scaling factor to create more or less distance between the ruler and its labels. If *None* then this method calculates defaults. The values are usually less than 5.0. :type mscale: Floating point number :param gridmode: If True, correct pixel position for CRPIX to get grid coordinates where the pixel at CRPIX is 0 :type gridmode: Boolean :param `**kwargs`: Set keyword arguments for the labels. The attributes for the ruler labels are set with these keyword arguments. :type `**kwargs`: Matplotlib keyword argument(s) :Raises: :exc:`Exception` *Rulers only suitable for maps with at least one spatial axis!* These rulers are only for plotting offsets as distances on a sphere for the current projection system. So we need at least one spatial axis and if there is only one spatial axis in the plot, then we need a matching spatial axis. :exc:`Exception` *Cannot make ruler with step size equal to zero!* Either the input of the step size is invalid or a wrong default was calculated (perhaps end point is equal to start point). :exc:`Exception` *Start point of ruler not in pixel limits!* :exc:`Exception` *End point of ruler not in pixel limits!* :Returns: A ruler object of class ruler which is added to the plot container with Plotversion's method :meth:`Plotversion.add`. This ruler object has two methods to change the properties of the line and the labels: * `setp_line(**kwargs)` -- Matplotlib keyword arguments for changing the line properties. * `setp_labels(**kwargs)` -- Matplotlib keyword arguments for changing the label properties. :Notes: A bisection is used to find a new marker position so that the distance to a previous position is *step*.. We use a formula of Thaddeus Vincenty, 1975, for the calculation of a distance on a sphere accurate over the entire sphere. :Examples: Create a ruler object and change its properties :: ruler2 = annim.Ruler(x1=x1, y1=y1, x2=x2, y2=y2, lambda0=0.5, step=2.0, fmt='%3d', mscale=-1.5, fliplabelside=True) ruler2.setp_labels(ha='left', va='center', color='b') ruler4 = annim.Ruler(pos1="23h0m 15d0m", pos2="22h0m 30d0m", lambda0=0.0, step=1, world=True, fmt=r"%4.0f^\prime", fun=lambda x: x*60.0, addangle=0) ruler4.setp_line(color='g') ruler4.setp_labels(color='m') # Force step size and labeling to be in minutes of arc. annim.Ruler(pos1='0h3m30s 6d30m', pos2='0h3m30s 7d0m', lambda0=0.0, step=5.0, units='arcmin', color='c') .. automethod:: setp_line .. automethod:: setp_label """ #----------------------------------------------------------------- def __init__(self, projection, mixpix, pxlim, pylim, aspectratio=1.0, pos1=None, pos2=None, rulersize=None, rulerangle=None, x1=None, y1=None, x2=None, y2=None, lambda0=0.5, step=None, world=False, angle=None, addangle=0.0, fmt=None, fun=None, units=None, fliplabelside=False, mscale=None, labelsintex=True, gridmode=False, **kwargs): self.ptype = "Ruler" self.x1 = None self.y1 = None self.x2 = None self.y2 = None self.x = [] self.y = [] self.xw = [] self.yw = [] self.stepsizeW = None self.label = [] self.offsets = [] # Store the offsets in degrees self.angle = None self.kwargs = {'clip_on' : True} # clip_on is buggy for plot() in MPL versions <= 0.98.3 change later self.tickdx = None self.tickdy = None self.mscale = None self.fun = None self.fmt = None self.linekwargs = {'color' : 'k'} self.kwargs.update(kwargs) # These are the kwargs for the labels self.aspectratio = aspectratio self.rulertitle = None self.gridmode = gridmode self.labelsintex = labelsintex # Recipe: # Are the start and endpoint in world coordinates or pixels? # Convert to pixels. # Calculate the central position in pixels # Calculate the central position in world coordinates (Xw,Yw) # Find a lambda in (x,y) = (x1,y1) + lambda*(x2-x1,y2-x1) # so that, if (x,y) <-> (xw,yw), the distance D((Xw,Yw), (xw,yw)) # is the step size on the ruler. def bisect(offset, lambda_s, Xw, Yw, x1, y1, x2, y2): """ We are looking for a value mu so that mu+lambda_s sets a pixel which corresponds to world coordinates that are 'offset' away from the start point set by lambda_s If lambda_s == 0 then we are in x1, x2. If lambda_s == 1 we are in x2, y2 """ mes = '' if offset >= 0.0: a = 0.0; b = 1.1 else: a = -1.1; b = 0.0 f1 = getdistance(a, lambda_s, Xw, Yw, x1, y1, x2, y2) - abs(offset) f2 = getdistance(b, lambda_s, Xw, Yw, x1, y1, x2, y2) - abs(offset) validconditions = f1*f2 < 0.0 if not validconditions: mes = "Found interval without a root for this step size" return None, mes tol = 1e-12 # Tolerance. Stop iteration if (b-a)/2 < tol N0 = 50 # Stop output with error message if number of iterations # exceeds this number # Initialize i = 0 fa = getdistance(a, lambda_s, Xw, Yw, x1, y1, x2, y2) - abs(offset) # The iteration itself while i <= N0: # The bisection p = a + (b-a)/2.0 fp = getdistance(p, lambda_s, Xw, Yw, x1, y1, x2, y2) - abs(offset) # Did we find a root? i += 1 if fp == 0.0 or (b-a)/2.0 < tol: # print 'Root is: ', p, fp # We found a root # print "Iterations: ", i break # Success..., leave the while loop if fa*fp > 0: a = p fa = fp else: b = p else: mes = 'Ruler bisection failed after %d iterations!' % N0 p = None return p, mes def DV(l1, b1, l2, b2): # Vincenty, Thaddeus, 1975, formula for distance on sphere accurate over entire sphere fac = numpy.pi / 180.0 l1 *= fac; b1 *= fac; l2 *= fac; b2 *= fac dlon = l2 - l1 a1 = numpy.cos(b2)*numpy.sin(dlon) a2 = numpy.cos(b1)*numpy.sin(b2) - numpy.sin(b1)*numpy.cos(b2)*numpy.cos(dlon) a = numpy.sqrt(a1*a1+a2*a2) b = numpy.sin(b1)*numpy.sin(b2) + numpy.cos(b1)*numpy.cos(b2)*numpy.cos(dlon) d = numpy.arctan2(a,b) return d*180.0/numpy.pi def tolonlat(x, y): # This function also sorts the spatial values in order # longitude, latitude if mixpix == None: xw, yw = projection.toworld((x,y)) xwo = xw # Store originals ywo = yw else: W = projection.toworld((x, y, mixpix)) xw = W[projection.lonaxnum-1] yw = W[projection.lataxnum-1] xwo = xw; ywo = yw if projection.lonaxnum > projection.lataxnum: xwo, ywo = ywo, xwo # Swap return xw, yw, xwo, ywo def topixel2(xw, yw): # Note that this conversion is only used to convert # start and end position, given in world coordinates, # to pixels. if mixpix == None: x, y = projection.topixel((xw,yw)) else: unknown = numpy.nan wt = (xw, yw, unknown) pixel = (unknown, unknown, mixpix) (wt, pixel) = projection.mixed(wt, pixel) x = pixel[0]; y = pixel[1] return x, y def getdistance(mu, lambda_s, Xw, Yw, x1, y1, x2, y2): lam = lambda_s + mu x = x1 + lam*(x2-x1) y = y1 + lam*(y2-y1) xw, yw, xw1, yw1 = tolonlat(x,y) return DV(Xw, Yw, xw, yw) def nicestep(x1, y1, x2, y2): # Assume positions in pixels xw1, yw1, dummyx, dummyy = tolonlat(x1,y1) xw2, yw2, dummyx, dummyy = tolonlat(x2,y2) step = None length = DV(xw1, yw1, xw2, yw2) # Nice numbers for dms should also be nice numbers for hms sec = numpy.array([30, 20, 15, 10, 5, 2, 1]) minut = sec deg = numpy.array([60, 30, 20, 15, 10, 5, 2, 1]) nicenumber = numpy.concatenate((deg*3600.0, minut*60.0, sec)) fact = 3600.0 d = length * fact step2 = 0.9*d/3.0 # We want at least four offsets on our ruler for p in nicenumber: k = int(step2/p) if k >= 1.0: step2 = k * p step = step2 break # Stop if we have a candidate # d = x2 - x1 # If nothing suitable then try something else if step == None: f = int(numpy.log10(d)) if d < 1.0: f -= 1 D3 = numpy.round(d/(10.0**f),0) if D3 == 3.0: D3 = 2.0 elif D3 == 6: D3 = 5.0 elif D3 == 7: D3 = 8 elif D3 == 9: D3 = 10 if D3 in [2,4,8]: k = 4 else: k = 5 step = (D3*10.0**f)/k return step/fact spatial = projection.types[0] in ['longitude', 'latitude'] or projection.types[1] in ['longitude', 'latitude'] if not spatial: raise Exception("Rulers only suitable for maps with at least one spatial axis!") # User entered units, then check conversion uf = None if not units is None: uf, errmes = unitfactor('degree', units) if uf is None: raise ValueError(errmes) if not pos1 is None: poswp = str2pos(pos1, projection, mixpix=mixpix, gridmode=self.gridmode) if poswp[3] != "": raise Exception(poswp[3]) # The result of the position parsing of str2pos is stored in 'poswp' # Its second element are the returned pixel coordinates. # (poswp[1]). # Note we required 1 position. Then the pixel coordinate we want is # poswp[1][0]. If we omit the last index then we end up with a sequence (of 1) # which cannot be processed further. Finally the pixel coordinate represents a # position in 2-dim. So the first element represents x (poswp[1][0][0]). pix = poswp[1][0] x1 = pix[0] y1 = pix[1] else: if x1 is None: x1 = pxlim[0]; world = False if y1 is None: y1 = pylim[0]; world = False if world: x1, y1 = topixel2(x1, y1) if not pos2 is None: poswp = str2pos(pos2, projection, mixpix=mixpix, gridmode=self.gridmode) if poswp[3] != "": raise Exception(poswp[3]) pix = poswp[1][0] x2 = pix[0] y2 = pix[1] else: if not rulersize is None: # We have two pixels to start with. Convert to long, lat # which serves as a starting point for the ruler. lon1, lat1, xwo1, ywo1 = tolonlat(x1, y1) swapped = lon1 != xwo1 # Find second point in world coordinates if rulerangle is None: rulerangle = 270.0 if not uf is None: rulersize /= uf # Find end point. Assume cdelt of long. is negative lon2, lat2 = dispcoord(lon1, lat1, rulersize, -1, rulerangle) if swapped: x2 = lat2 y2 = lon2 # Swap back else: x2 = lon2 y2 = lat2 x2, y2 = topixel2(x2, y2) else: if x2 is None: x2 = pxlim[1]; world = False if y2 is None: y2 = pylim[1]; world = False if world: x2, y2 = topixel2(x2, y2) #print "DV", DV(23*15,15, 22*15, 30)*60.0 # Get a step size for nice offsets if step is None: stepsizeW = nicestep(x1, y1, x2, y2) else: stepsizeW = step if step == 0.0: raise Exception("Cannot make ruler with step size equal to zero!") # Look for suitable units (degrees, arcmin, arcsec) if nothing is # specified in the call. Note that 'stepsizeW' is in degrees. uf = None if units != None: uf, errmes = unitfactor('degree', units) if uf is None: raise ValueError(errmes) if uf != 1.0: fun = lambda x: x*uf # Input in 'units' but must be degrees for further processing if not step is None: stepsizeW /= uf # because step was in units of 'units'. Must be deg. if fmt is None: if uf == 1.0: if labelsintex: fmt = r"%4.0f^{\circ}" else: fmt = "%4.0f\u00B0" elif uf == 60.0: # Write labels in arcmin if labelsintex: fmt = r"%4.0f^{\prime}" else: fmt = r"%4.0f'" elif uf == 3600.0: # Write labels in arcsec if labelsintex: fmt = r"%4.0f^{\prime\prime}" else: fmt = r"%4.0f''" else: raise ValueError("Only degree, arcmin and arcsec allowed") if fun is None and fmt is None: if labelsintex: fmt = r"%4.0f^{\circ}" else: fmt = "%4.0f\u00B0" if abs(stepsizeW) < 1.0: # Write labels in arcmin fun = lambda x: x*60.0 if labelsintex: fmt = r"%4.0f^{\prime}" else: fmt = r"%4.0f'" if abs(stepsizeW) < 1.0/60.0: # Write labels in arcsec fun = lambda x: x*3600.0 if labelsintex: fmt = r"%4.0f^{\prime\prime}" else: fmt = r"%4.0f''" elif fmt is None: # A function but not a format. Then a default format fmt = '%g' # Check whether the start- and end point of the ruler are inside the frame start_in = isinside(x1, y1, pxlim, pylim) #start_in = (pxlim[0]-0.5 <= x1 <= pxlim[1]+0.5) and (pylim[0]-0.5 <= y1 <= pylim[1]+0.5) if not start_in: raise Exception("Start point of ruler not in pixel limits!") end_in = isinside(x2, y2, pxlim, pylim) #end_in = (pxlim[0]-0.5 <= x2 <= pxlim[1]+0.5) and (pylim[0]-0.5 <= y2 <= pylim[1]+0.5) if not end_in: raise Exception("End point of ruler not in pixel limits!") # Ticks perpendicular to ruler line. Prependicular is with respect to # square pixels, so correct these first for their aspect ratio to find # the right angle. defangle = 180.0 * numpy.arctan2(y2-y1, (x2-x1)/aspectratio) / numpy.pi - 90.0 l1 = pxlim[1] - pxlim[0] + 1.0; l1 /= 100.0 l2 = pylim[1] - pylim[0] + 1.0; l2 /= 100.0 ll = max(l1,l2) dx = ll*numpy.cos(defangle*numpy.pi/180.0)*aspectratio dy = ll*numpy.sin(defangle*numpy.pi/180.0) if fliplabelside: dx = -dx dy = -dy if angle == None: phi = defangle else: phi = angle phi += addangle defkwargs = {'fontsize':10, 'rotation':phi} if defangle+90.0 in [270.0, 90.0, -90.0, -270.0]: if fliplabelside: defkwargs.update({'va':'center', 'ha':'right'}) else: defkwargs.update({'va':'center', 'ha':'left'}) if mscale == None: mscale = 1.5 elif defangle+90.0 in [0.0, 180.0, -180.0]: if fliplabelside: defkwargs.update({'va':'bottom', 'ha':'center'}) else: defkwargs.update({'va':'top', 'ha':'center'}) mscale = 1.5 else: defkwargs.update({'va':'center', 'ha':'center'}) if mscale == None: mscale = 2.5 defkwargs.update(kwargs) #ruler = Rulerstick(x1, y1, x2, y2, defangle, dx, dy, mscale, **defkwargs) self.x1 = x1; self.x2 = x2; self.y1 = y1; self.y2 = y2 self.angle = defangle self.tickdx = dx; self.tickdy = dy self.mscale = mscale self.kwargs.update(defkwargs) self.fmt = fmt self.fun = fun self.flip = fliplabelside lambda_s = lambda0 x0 = x1 + lambda_s*(x2-x1) y0 = y1 + lambda_s*(y2-y1) Xw, Yw, xw1, yw1 = tolonlat(x0, y0) self.append(x0, y0, 0.0, fmt%0.0) self.appendW(xw1, yw1) # Store in original order i.e. not sorted self.stepsizeW = stepsizeW # Needed elsewhere so store as an attribute # Find the mu on the straight ruler line for which the distance between # the position defined by mu and the center point (lambda0) is 'offset' # Note that these distances are calculated on a sphere for sign in [+1.0, -1.0]: mu = 0.0 offset = 0.0 lamplusmu = lambda_s + mu while mu != None and (0.0 <= lamplusmu <= 1.0): offset += sign*stepsizeW mu, mes = bisect(offset, lambda_s, Xw, Yw, x1, y1, x2, y2) if mu != None: lamplusmu = lambda_s + mu if 0.0 <= lamplusmu <= 1.0: x = x1 + (lamplusmu)*(x2-x1) y = y1 + (lamplusmu)*(y2-y1) if fun != None: off = fun(offset) else: off = abs(offset) self.append(x, y, offset, fmt%off, labelsintex) xw, yw, xw1, yw1 = tolonlat(x, y) self.appendW(xw1, yw1) elif sign == -1.0: break # raise Exception, mes self.pxlim = pxlim self.pylim = pylim def set_title(self, rulertitle, **kwargs): x1 = self.x1; x2 = self.x2 y1 = self.y1; y2 = self.y2 defangle = 180.0 * numpy.arctan2(y2-y1, (x2-x1)/self.aspectratio) / numpy.pi xt = x1 + 0.5*(x2-x1) yt = y1 + 0.5*(y2-y1) self.xt = xt self.yt = yt self.titleangle = defangle self.rulertitle = rulertitle self.titlekwargs = kwargs
[docs] def setp_line(self, **kwargs): #----------------------------------------------------------------- """ Set the ruler line properties. The keyword arguments are Matplotlib keywords for :class:`Line2D` objects. :param kwargs: Keyword argument(s) for changing the default properties of the ruler line. This line is a :class:`Line2D` Matplotlib object with attributes like *linewidth*, *color* etc. :type kwargs: Python keyword arguments """ #----------------------------------------------------------------- self.linekwargs.update(kwargs)
[docs] def setp_label(self, **kwargs): #----------------------------------------------------------------- """ Set the ruler label properties. The keyword arguments are Matplotlib keywords for :class:`Text` objects. Note that the properties apply to all labels. It is not possible to address a separate label. :param kwargs: Keyword argument(s) for changing the default properties of the ruler labels. This line is a :class:`Text` Matplotlib object with attributes like *fontsize*, *color* etc. :type kwargs: Python keyword arguments """ #----------------------------------------------------------------- self.kwargs.update(kwargs)
def append(self, x, y, offset, label, labelsintex=True): self.x.append(x) self.y.append(y) self.offsets.append(offset) if labelsintex: label = r"$%s$"%label self.label.append(label) def appendW(self, xw, yw): self.xw.append(xw) self.yw.append(yw) def plot(self, frame): """ Plot one ruler object in the current frame """ frame.plot((self.x1,self.x2), (self.y1,self.y2), '-', **self.linekwargs) dx = self.tickdx dy = self.tickdy #self.frame.plot( [self.x1, self.x1+dx], [self.y1, self.y1+dy], '-', **self.linekwargs) #self.frame.plot( [self.x2, self.x2+dx], [self.y2, self.y2+dy], '-', **self.linekwargs) for x, y, label in zip(self.x, self.y, self.label): frame.plot( [x, x+dx], [y, y+dy], '-', color='k') frame.text(x+self.mscale*dx, y+self.mscale*dy, label, **self.kwargs) if not self.rulertitle is None: if self.flip: titlekwargs = {'va':'top', 'ha':'center', 'rotation_mode':'anchor'} else: titlekwargs = {'va':'bottom', 'ha':'center', 'rotation_mode':'anchor'} titlekwargs.update(self.titlekwargs) titleangle = self.titleangle if titleangle > 135.0: titleangle -= 180.0 titlekwargs.update({'va':'top'}) if titleangle <= -135.0: titleangle += 180.0 titlekwargs.update({'va':'top'}) try: # For users with an old Matplotlib frame.text(self.xt-dx, self.yt-dy, self.rulertitle, rotation=titleangle, **titlekwargs) except: pass