Logo Search packages:      
Sourcecode: mascyma version File versions  Download package

lines.py

00001 """
This module contains all the 2D line class which can draw with a
variety of line styles, markers and colors
"""

from __future__ import generators
from __future__ import division

import sys

from Numeric import Float, alltrue, arange, array, logical_and,\
     nonzero, reshape, resize, searchsorted, take, Int, asarray, ones

from artist import Artist
from cbook import True, False
from transforms import Bound1D, Bound2D, Transform

lineStyles = {'-':1, '--':1, '-.':1, ':':1}
lineMarkers =    {'.':1, ',':1, 'o':1, '^':1,
                  'v':1, '<':1, '>':1, 's':1, '+':1}

class Line2D(Artist):

    def __init__(self, dpi, bbox, xdata, ydata,
                 linewidth=0.5, linestyle='-',
                 color='b', 
                 marker=None, markersize=6,
                 markeredgecolor=None, markerfacecolor=None,
                 transx=Transform(),
                 transy=Transform(),
                 ):
        """
        dpi is a DPI instance
        bbox is a Bound2D instance with the display bounding box xdata
        xdata is a sequence of x data
        ydata is a sequence of y data
        linewidth is the width of the line in points
        linestle is one of lineStyls
        marker is one of lineMarkers or None
        markersize is the size of the marker in points
        markeredgecolor  and markerfacecolor can be any color arg 

        Public attributes:
        transx : a Transorm instance
        transy : a Transorm instance
        """
        Artist.__init__(self, dpi, bbox)
        
        #convert sequences to numeric arrays

        self._linestyle = linestyle
        self._linewidth = linewidth
        self._color = color
        self._markersize = markersize
        if markeredgecolor is None: markeredgecolor=color
        
        self._markerfacecolor = markerfacecolor
        self._markeredgecolor = markeredgecolor

        self.verticalOffset = None        
        self.set_data(xdata, ydata)
        self.transx = transx
        self.transy = transy
        self._lineStyles =  {
            '-'  : self._draw_solid,
            '--' : self._draw_dashed, 
            '-.' : self._draw_dash_dot, 
            ':'  : self._draw_dotted,
            None : self._draw_nothing}

        self._markers =  {
            '.'  : self._draw_point,
            ','  : self._draw_pixel, 
            'o'  : self._draw_circle, 
            'v'  : self._draw_triangle_down, 
            '^'  : self._draw_triangle_up, 
            '<'  : self._draw_triangle_left, 
            '>'  : self._draw_triangle_right, 
            's'  : self._draw_square,
            '+'  : self._draw_plus,
            None : self._draw_nothing
            }

        if not self._lineStyles.has_key(linestyle):
            print >>sys.stderr, 'Unrecognized line style', linestyle
        if not self._markers.has_key(marker):
            print >>sys.stderr, 'Unrecognized marker style', marker

        self._marker = marker
        self._lineFunc = self._lineStyles.get(linestyle, self._draw_nothing)
        self._markerFunc = self._markers.get(marker, self._draw_nothing)
        self._useDataClipping = True
        
    def get_window_extent(self):
        x, y = self._get_numeric_clipped_data_in_range()
        x = self.transx.positions(x)
        y = self.transy.positions(y)
        if self.verticalOffset is not None:
            y += self.transOffset.positions(self.verticalOffset)

        left = min(x)
        bottom = min(y)
        width = max(x) - left
        height = max(y) - bottom

        # correct for marker size, if any
        if self._marker is not None:
            ms = self._markersize/72.0*self.dpi.get()
            left -= ms/2
            bottom -= ms/2
            width += ms
            height += ms
        return Bound2D( left, bottom, width, height)
        
    def set_data(self, x, y):
        try: del self._xc, self._yc
        except AttributeError: pass

        self._x = asarray(x, Float)
        self._y = asarray(y, Float)
        if len(self._y)==1 and len(self._x)>1:
            self._y = self._y*ones(self._x.shape, Float)

        self._xsorted = self._is_sorted(self._x)

    def set_data_clipping(self, b):
        'b is a boolean that sets whether data clipping is on'
        self._useDataClipping = b
        
    def set_vertical_offset(self, voff, transOffset=None):
        self.verticalOffset = voff
        if transOffset is not None:
            self.transOffset = transOffset
        else:
            self.transOffset = self.transy
            
        
    def _is_sorted(self, x):
        "return true if x is sorted"
        if len(x)<2: return 1
        return alltrue(x[1:]-x[0:-1]>=0)
    
    def _get_numeric_clipped_data_in_range(self):
        # if the x or y clip is set, only plot the points in the
        # clipping region
        try: self._xc, self._yc
        except AttributeError: x, y = self._x, self._y
        else: x, y = self._xc, self._yc

            
        return x, y

    def _draw(self, drawable, *args, **kwargs):
        
        x, y = self._get_numeric_clipped_data_in_range()

        if len(x)==0: return 

        xt = self.transx.positions(x)
        yt = self.transy.positions(y)
        if self.verticalOffset is not None:
            yt += self.transOffset.positions(self.verticalOffset)


        gc = drawable.new_gc()
        gc.set_foreground(self._color)
        gc.set_linewidth(self._linewidth)
        if self._clipOn:
            gc.set_clip_rectangle(self.bbox.get_bounds())
        self._lineFunc(drawable, gc, xt, yt)

        if self._marker is not None:
            gc = drawable.new_gc()
            gc.set_foreground(self._markeredgecolor)
            gc.set_linewidth(self._linewidth)
            if self._clipOn:
                gc.set_clip_rectangle(self.bbox.get_bounds())
            self._markerFunc(drawable, gc, xt, yt)

    def _derived_draw(self, drawable, gc, x, y):
        raise NotImplementedError, 'Line2D is a pure base class.  ' + \
              'You must instantiate a derived class'


    def get_xdata(self): return self._x
    def get_ydata(self):  return self._y
    def get_linewidth(self): return self._linewidth
    def get_linestyle(self): return self._linestyle
    def get_markersize(self): return self._markersize
    def get_markerfacecolor(self): return self._markerfacecolor
    def get_markeredgecolor(self): return self._markeredgecolor
    def get_color(self): return self._color

        
    def _set_clip(self):

        if not self._useDataClipping: return
        try: self._xmin, self._xmax
        except AttributeError: indx = arange(len(self._x))
        else:
            if len(self._x)==1:
                indx = 0
            elif self._xsorted:
                # for really long signals, if we know they are sorted
                # on x we can save a lot of time using search sorted
                # since the alternative approach requires 3 O(len(x) ) ops
                indMin, indMax = searchsorted(
                    self._x, array([self._xmin, self._xmax]))

                skip = 0
                if self._lod:
                    # if level of detail is on, decimate the data
                    # based on pixel width
                    l, b, w, h = self.get_window_extent().get_bounds()
                    skip = int((indMax-indMin)/w)                    
                if skip>0:  indx = arange(indMin, indMax, skip)
                else: indx = arange(indMin, indMax)
            else:
                indx = nonzero(
                    logical_and( self._x>=self._xmin,
                                       self._x<=self._xmax ))
        

        self._xc = take(self._x, indx)
        self._yc = take(self._y, indx)

        # y data clipping for connected lines can introduce horizontal
        # line artifacts near the clip region.  If you really need y
        # clipping for efficiency, consider using plot(y,x) instead.
        if ( self._yc.shape==self._xc.shape and 
             self._linestyle is None):
            try: self._ymin, self._ymax
            except AttributeError: indy = arange(len(self._yc))
            else: indy = nonzero(
                logical_and(self._yc>=self._ymin,
                                  self._yc<=self._ymax ))
        else:
            indy = arange(len(self._yc))
            
        self._xc = take(self._xc, indy)
        self._yc = take(self._yc, indy)

    def set_color(self, color):
        self._color = color

    def set_linewidth(self, w):
        self._linewidth = w

    def set_linestyle(self, s):
        self._linestyle = s
        self._lineFunc = self._lineStyles.get(self._linestyle, '-')
    def set_markeredgecolor(self, ec):
        self._markeredgecolor = ec

    def set_markerfacecolor(self, fc):
        self._markerfacecolor = fc

    def set_markersize(self, sz):
        self._markersize = sz

    def set_xdata(self, x):
        self.set_data(x, self._y)

    def set_ydata(self, y):
        self.set_data(self._x, y)
        
    def set_xclip(self, xmin, xmax):
        self._xmin, self._xmax = xmin, xmax
        self._set_clip()

    def set_yclip(self, ymin, ymax):
        self._ymin, self._ymax = ymin, ymax
        self._set_clip()

    def _draw_nothing(self, drawable, gc, xt, yt):
        pass
    
    def _draw_solid(self, drawable, gc, xt, yt):
        gc.set_linestyle('solid')
        drawable.draw_lines(gc, xt,yt)

    def _draw_dashed(self, drawable, gc, xt, yt):
        dashes = self.dpi.get()/72.0*array([6.0, 6.0])
        #gc.set_dashes(0, dashes)  #  gdk.LINE_ON_OFF_DASH
        gc.set_linestyle('dashed')
        gc.set_capstyle('butt')   #  gdk.CAP_BUTT
        gc.set_joinstyle('miter')  #  gdk.JOIN_MITER
        drawable.draw_lines(gc, xt, yt)

    def _draw_dash_dot(self, drawable, gc, xt, yt):
        dashes = self.dpi.get()/72.0*array([3.0, 5.0, 1.0, 5.0])
        #gc.set_dashes(0, dashes)  #3 on, 5 off, 1 on, 5 off
        gc.set_linestyle('dashdot')
        gc.set_capstyle('butt')   
        gc.set_joinstyle('miter') 
        drawable.draw_lines(gc, xt, yt)

    def _draw_dotted(self, drawable, gc, xt, yt):
        dashes = self.dpi.get()/72.0*array([1.0, 2.0])
        gc.set_linestyle('dotted')
        #gc.set_dashes(0, dashes)  
        gc.set_capstyle('butt')   
        gc.set_joinstyle('miter') 
        drawable.draw_lines(gc, xt, yt)

    def _draw_point(self, drawable, gc, xt, yt):
        for (x,y) in zip(xt, yt):
            drawable.draw_arc(gc, None, x, y, 1, 1, 0.0, 360.0)


    def _draw_pixel(self, drawable, gc, xt, yt):
        for (x,y) in zip(xt, yt):
            drawable.draw_point(gc, x, y)


    def _draw_circle(self, drawable, gc, xt, yt):
        w = h = self._markersize*self.dpi.get()/72.0
        for (x,y) in zip(xt, yt):
            drawable.draw_arc(gc, self._markerfacecolor,
                              x, y, w, h, 0.0, 360.0)

    def _draw_triangle_up(self, drawable, gc, xt, yt):
        offset = 0.5*self._markersize*self.dpi.get()/72.0
        
        for (x,y) in zip(xt, yt):
            verts = ( (x, y+offset),
                      (x-offset, y-offset),
                      (x+offset, y-offset) )
            drawable.draw_polygon(gc, self._markerfacecolor, verts)


    def _draw_triangle_down(self, drawable, gc, xt, yt):
        offset = 0.5*self._markersize*self.dpi.get()/72.0
        for (x,y) in zip(xt, yt):            
            verts = ( (x-offset, y+offset),
                      (x+offset, y+offset),
                      (x, y-offset))
            drawable.draw_polygon(gc, self._markerfacecolor, verts)

    def _draw_triangle_left(self, drawable, gc, xt, yt):
        offset = 0.5*self._markersize*self.dpi.get()/72.0
        for (x,y) in zip(xt, yt):            
            verts = ( (x-offset, y),
                      (x+offset, y-offset),
                      (x+offset, y+offset))
            drawable.draw_polygon(gc, self._markerfacecolor, verts)


    def _draw_triangle_right(self, drawable, gc, xt, yt):
        offset = 0.5*self._markersize*self.dpi.get()/72.0        
        for (x,y) in zip(xt, yt):            
            verts = ( (x+offset, y),
                      (x-offset, y-offset),
                      (x-offset, y+offset))
            drawable.draw_polygon(gc, self._markerfacecolor, verts)

            

    def _draw_square(self, drawable, gc, xt, yt):
        side = self.dpi.get()/72.0*self._markersize
        offset = side*0.5
        for (x,y) in zip(xt, yt):            
            drawable.draw_rectangle(
                gc, self._markerfacecolor,
                x-offset, y-offset, side, side) 

    def _draw_plus(self, drawable, gc, xt, yt):
        offset = self._markersize*self.dpi.get()/72.0
        # todo: unify marker size args; use default vals for line
        # sytles in dict?
        for (x,y) in zip(xt, yt):
            drawable.draw_line(gc, x-offset, y, x+offset, y)
            drawable.draw_line(gc, x, y-offset, x, y+offset)

    def copy_properties(self, line):

        self._linestyle = line._linestyle
        self._linewidth = line._linewidth
        self._color = line._color
        self._markersize = line._markersize        
        self._markerfacecolor = line._markerfacecolor
        self._markeredgecolor = line._markeredgecolor

        self._linestyle = line._linestyle
        self._marker = line._marker
        self._lineFunc = line._lineStyles[line._linestyle]
        self._markerFunc = line._markers[line._marker]
        self._useDataClipping = line._useDataClipping

Generated by  Doxygen 1.6.0   Back to index